Skip to content

Commit

Permalink
Restructured code
Browse files Browse the repository at this point in the history
  • Loading branch information
crappycrypto committed Aug 31, 2014
1 parent 7d20cbf commit 841bd33
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 46 deletions.
47 changes: 46 additions & 1 deletion README.md
@@ -1,6 +1,51 @@
wincrypto
=========

Windows Crypto API compatible decryption/encryption for python
Windows Crypto API compatible decryption/encryption for python. The targeted crypto provider is PROV_RSA_AES.

Implemented algorithms:

- CALG_RC4
- CALG_AES128
- CALG_AES192
- CALG_AES256
- CALG_RSA_KEYX
- CALG_MD5
- CALG_SHA1

Implemented functions:

- CryptImportKey
- CryptExportKey
- CryptEncrypt
- CryptDecrypt
- CryptGetKeyParam (incomplete)
- CryptCreateHash
- CryptHashData
- CryptGetHashParam
- CryptDeriveKey

An example of how to use this package:

```python
from wincrypto import CryptCreateHash, CryptHashData, CryptDeriveKey, CryptEncrypt, CryptImportKey, CryptExportKey
from wincrypto.constants import CALG_SHA1, CALG_AES_256, bType_SIMPLEBLOB

#derive key from password
sha1_hasher = CryptCreateHash(CALG_SHA1)
CryptHashData(sha1_hasher, 'Password')
aes_key = CryptDeriveKey(sha1_hasher, CALG_AES_256)

#encrypt data using key
encrypted_data = CryptEncrypt(aes_key, 'secret data')

#Import a PUBLICKEYBLOB and export the AES key as SIMPLEBLOB
TEST_RSA_PUBLIC_MSKEYBLOB = '0602000000a40000525341310004000001000100d1537575000617a37093cec958e8adedd347b5812f' \
'702595fc02fbb870f6a17a26780f9147a6cd938dffff842a1427f8200621f822caaf9b338b4bb3dbda' \
'ce58eedfc7b29b91a1f5ce628657f30f3feb9d909a1a00bd484f628f2db38087eec2f6bb4df1df024b' \
'19af1c97d316c86073c972059d65bc2b47f97e5462a2e8029a'.decode('hex')
rsa_pub_key = CryptImportKey(TEST_RSA_PUBLIC_MSKEYBLOB)
encrypted_aes_key = CryptExportKey(aes_key, rsa_pub_key, bType_SIMPLEBLOB)
```

[![Build Status](https://travis-ci.org/crappycrypto/wincrypto.png)](https://travis-ci.org/crappycrypto/wincrypto)
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -2,7 +2,7 @@


setup(name='wincrypto',
version='0.1a',
version='0.2a',
description='Windows Crypto API compatible decryption/encryption',
url='http://github.com/crappycrypto/wincrypto',
author='crappycrypto',
Expand Down
4 changes: 2 additions & 2 deletions tests/algorithms.py
Expand Up @@ -4,7 +4,7 @@
from wincrypto.algorithms import symmetric_algorithms, hash_algorithms
from wincrypto.api import CryptImportKey, CryptExportKey, CryptCreateHash, CryptDeriveKey, \
CryptHashData
from wincrypto.definitions import bType_PLAINTEXTKEYBLOB, CALG_MD5, CALG_RC4, CALG_SHA1, CALG_AES_192
from wincrypto.constants import bType_PLAINTEXTKEYBLOB, CALG_MD5, CALG_RC4, CALG_SHA1, CALG_AES_192, bType_SIMPLEBLOB


TEST_RSA_PRIVATE_PEM = '''-----BEGIN RSA PRIVATE KEY-----
Expand Down Expand Up @@ -45,7 +45,7 @@ def test_export_import_simple(self):
rsa_key = wincrypto.algorithms.RSA_KEYX.from_pem(TEST_RSA_PRIVATE_PEM)
for algorithm in symmetric_algorithms:
instance = algorithm('A' * algorithm.key_len)
blob = CryptExportKey(instance, rsa_key, bType_PLAINTEXTKEYBLOB)
blob = CryptExportKey(instance, rsa_key, bType_SIMPLEBLOB)
instance2 = CryptImportKey(blob, rsa_key)
self.assertEqual(instance.key, instance2.key)

Expand Down
15 changes: 9 additions & 6 deletions tests/native.py
Expand Up @@ -2,11 +2,11 @@

if platform.system() == 'Windows':
import unittest
from wincrypto import native, definitions, api
from wincrypto import native, constants, api
import wincrypto
from wincrypto.algorithms import symmetric_algorithms, hash_algorithms
import wincrypto.api
from wincrypto.definitions import bType_PLAINTEXTKEYBLOB, bType_SIMPLEBLOB, bType_PRIVATEKEYBLOB, KP_ALGID, \
from wincrypto.constants import bType_PLAINTEXTKEYBLOB, bType_SIMPLEBLOB, bType_PRIVATEKEYBLOB, KP_ALGID, \
KP_KEYLEN, HP_HASHSIZE, HP_ALGID


Expand All @@ -33,7 +33,10 @@
WMnOk3CjFwYAdXVT0QIDAQAB
-----END PUBLIC KEY-----'''

TEST_RSA_PUBLIC_MSKEYBLOB = '\x06\x02\x00\x00\x00\xa4\x00\x00RSA1\x00\x04\x00\x00\x01\x00\x01\x00\xd1Suu\x00\x06\x17\xa3p\x93\xce\xc9X\xe8\xad\xed\xd3G\xb5\x81/p%\x95\xfc\x02\xfb\xb8p\xf6\xa1z&x\x0f\x91G\xa6\xcd\x93\x8d\xff\xff\x84*\x14\'\xf8 \x06!\xf8"\xca\xaf\x9b3\x8bK\xb3\xdb\xda\xceX\xee\xdf\xc7\xb2\x9b\x91\xa1\xf5\xceb\x86W\xf3\x0f?\xeb\x9d\x90\x9a\x1a\x00\xbdHOb\x8f-\xb3\x80\x87\xee\xc2\xf6\xbbM\xf1\xdf\x02K\x19\xaf\x1c\x97\xd3\x16\xc8`s\xc9r\x05\x9de\xbc+G\xf9~Tb\xa2\xe8\x02\x9a'
TEST_RSA_PUBLIC_MSKEYBLOB = '0602000000a40000525341310004000001000100d1537575000617a37093cec958e8adedd347b5812f' \
'702595fc02fbb870f6a17a26780f9147a6cd938dffff842a1427f8200621f822caaf9b338b4bb3dbda' \
'ce58eedfc7b29b91a1f5ce628657f30f3feb9d909a1a00bd484f628f2db38087eec2f6bb4df1df024b' \
'19af1c97d316c86073c972059d65bc2b47f97e5462a2e8029a'.decode('hex')
TEST_RSA_PRIVATE_MSKEYBLOB = '\x07\x02\x00\x00\x00\xa4\x00\x00RSA2\x00\x04\x00\x00\x01\x00\x01\x00\xd1Suu\x00\x06\x17\xa3p\x93\xce\xc9X\xe8\xad\xed\xd3G\xb5\x81/p%\x95\xfc\x02\xfb\xb8p\xf6\xa1z&x\x0f\x91G\xa6\xcd\x93\x8d\xff\xff\x84*\x14\'\xf8 \x06!\xf8"\xca\xaf\x9b3\x8bK\xb3\xdb\xda\xceX\xee\xdf\xc7\xb2\x9b\x91\xa1\xf5\xceb\x86W\xf3\x0f?\xeb\x9d\x90\x9a\x1a\x00\xbdHOb\x8f-\xb3\x80\x87\xee\xc2\xf6\xbbM\xf1\xdf\x02K\x19\xaf\x1c\x97\xd3\x16\xc8`s\xc9r\x05\x9de\xbc+G\xf9~Tb\xa2\xe8\x02\x9a\x9b\\tcH\x8aa\x01\xac\x9b\x12\x11\x8f\xc6i\x8bb\xf6XoJ{\xf9uW\x15\xcc[\x01P\xdd\x86\xd9L\xbb\xa2\x13\xc3{\xa0\xf5\xe1K\x86:\xa01h\xf4\x1a\x89\xbd?\xd4J\xc2\xc5\x9f\x9by\x9a\xb6J\xb5\x03\x9a\xd9$1U\xff\x069\x0e\x1c\xf2\xf3\xcf\x8b;\xd6\x9e%\xca\xcd\xa5\xa5\xd6\x96\xc3)E\xba\x0b\xbe\xeb9\xa2\xb4*\xffw\xb8\xf3\xc9%\x0f}\xcc\xfb\x13\x83WL\xf8\x9fh\x10_D\xde\xf2w\xe3LAz\xd9\xebc?\xc9\xf64\xbb\xea\xa4\xc2@\xfd\xf2\x1b\x04J\r\xaa\x13\x93%\x1cxD\xd6\xdc\xddl\xbb\xb1An^\x83\xe3\xdf\x15\x9f\xa9\x11\x98e-\xd1\x8f\x1c\x8fIBB\xbd\x89\x91<\xba\x12^\x85\x8a\xb3]\xcfh\xae\r~\x07lcD3\x9a9\xe7\xb5\'\xe0e1\xb6\x1c\x93\xe1\xc4+\xf4\xe51b\xf8\xed\x15\xf7h\xaa\x00\xc0]\x17^\xbd\x13\x8d\x0f\xc3\xf7E\xa6\xb8\x83\xe4`\x04\n\x80O\x90A\xe8N{~\x88\xc8\xac\xaf\x03`\x0b\x00\xff\\A\x16\xba\x86|n\x89a\xa4P\x0e\x042\xd5\xe8\xa5\xa6\x9a\x15}?Q\x08\x8e\x83I\x89\x97\xa2n\xb8X\xf4\x16D>q\x1d\xac;\x05\x8c\xc3\x8c\x18\x842\xa2]}h3D\xb8c\xf3\x14\x9aE\x949\x87\xbf\xf1P\xef\x81\xf5=\x80\xdb\xdd\x7f\xe8\xfb2J\xa7\xbc\x8a\xd6PK\x184\x07\xfd\xc0\x93\xaf\rW\xd7\x0c\xcc\x15\x7f\x19*\xd0}\x82\xdai\xaf\xa88\x86N\xbe\xaew\xa1\x97\x1coI\x16\xd4\xfb\xf1R\xce\xa8\x8eUX\xbd\x1f\x93:\x9b*\xa3p(\xdf\xc6u\xd5Rip\xa0\xfe\xd8\xebCsV:p\xc7\t\t\x06\x82i\x14\xa5\xda\xa5#-\xd5\xbb\x08(kW\xd3\xf2xM\xfa\xb7\xdf[\x1a\x1aR\xfc7\xa0\xd6\x990B'

TEST_DATA = str(bytearray(range(64))) # 64 is a multiple of most blocksizes
Expand Down Expand Up @@ -62,7 +65,7 @@ def test_native_simplekeyblob(self):
rsa_hkey = native.CryptImportKey(self.hprov, rsa_blob)
for algorithm in symmetric_algorithms:
instance = algorithm('A' * algorithm.key_len)
blob = wincrypto.api.CryptExportKey(instance, rsa_key.key, bType_SIMPLEBLOB)
blob = wincrypto.api.CryptExportKey(instance, rsa_key, bType_SIMPLEBLOB)
hkey = native.CryptImportKey(self.hprov, blob, rsa_hkey)
c = native.CryptEncrypt(hkey, TEST_DATA)
p = instance.decrypt(c)
Expand Down Expand Up @@ -118,14 +121,14 @@ def test_hash_native_python(self):
for algorithm in hash_algorithms:
hCryptHash = native.CryptCreateHash(self.hprov, algorithm.alg_id)
native.CryptHashData(hCryptHash, TEST_DATA)
native_hash_val = native.CryptGetHashParam(hCryptHash, definitions.HP_HASHVAL)
native_hash_val = native.CryptGetHashParam(hCryptHash, constants.HP_HASHVAL)
native_hash_size = native.CryptGetHashParam(hCryptHash, HP_HASHSIZE)
native_hash_algid = native.CryptGetHashParam(hCryptHash, HP_ALGID)
native.CryptDestroyHash(hCryptHash)

md5_hash = wincrypto.api.CryptCreateHash(algorithm.alg_id)
wincrypto.api.CryptHashData(md5_hash, TEST_DATA)
python_hash = wincrypto.api.CryptGetHashParam(md5_hash, definitions.HP_HASHVAL)
python_hash = wincrypto.api.CryptGetHashParam(md5_hash, constants.HP_HASHVAL)
python_hash_size = wincrypto.api.CryptGetHashParam(md5_hash, HP_HASHSIZE)
python_hash_algid = wincrypto.api.CryptGetHashParam(md5_hash, HP_ALGID)

Expand Down
26 changes: 13 additions & 13 deletions wincrypto/algorithms.py
Expand Up @@ -8,10 +8,11 @@
from Crypto.PublicKey import RSA
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse

from wincrypto.definitions import RSAPUBKEY, RSAPUBKEY_s, RSAPUBKEY_MAGIC, PUBLICKEYSTRUC_s, bType_PUBLICKEYBLOB, \
from wincrypto.constants import RSAPUBKEY, RSAPUBKEY_s, RSAPUBKEY_MAGIC, PUBLICKEYSTRUC_s, bType_PUBLICKEYBLOB, \
CUR_BLOB_VERSION, CALG_RSA_KEYX, PRIVATEKEYBLOB_MAGIC, PRIVATEKEYBLOB, bType_PRIVATEKEYBLOB, bType_PLAINTEXTKEYBLOB, \
bType_SIMPLEBLOB, CALG_RC4, CALG_AES_128, CALG_AES_192, CALG_AES_256, CALG_MD5, CALG_SHA1
from wincrypto.util import add_pkcs5_padding, remove_pkcs5_padding
bType_SIMPLEBLOB, CALG_RC4, CALG_AES_128, CALG_AES_192, CALG_AES_256, CALG_MD5, CALG_SHA1, ALG_CLASS_HASH, \
ALG_CLASS_KEY_EXCHANGE, ALG_CLASS_DATA_ENCRYPT
from wincrypto.util import add_pkcs5_padding, remove_pkcs5_padding, GET_ALG_CLASS


class HCryptKey(object):
Expand Down Expand Up @@ -120,17 +121,15 @@ def export_plaintextkeyblob(self):
def import_simpleblob(cls, data, hPubKey):
assert struct.unpack('<I', data[:4])[0] == CALG_RSA_KEYX
assert hPubKey
pkcs_1_encrypted_key = data[4:][::-1]
c = Crypto.Cipher.PKCS1_v1_5.new(hPubKey)
key = c.decrypt(pkcs_1_encrypted_key, None)
key = hPubKey.decrypt(data[4:])
return cls(key)

def export_simpleblob(self, rsa_key):
result = PUBLICKEYSTRUC_s.pack(bType_SIMPLEBLOB, CUR_BLOB_VERSION, self.alg_id)
if rsa_key.alg_id != CALG_RSA_KEYX:
raise ValueError('SIMPLEBLOB export only supported under RSA key')
result += struct.pack('<I', CALG_RSA_KEYX)
c = Crypto.Cipher.PKCS1_v1_5.new(rsa_key)
encrypted_key = c.encrypt(self.key)
result += encrypted_key[::-1]
result += rsa_key.encrypt(self.key)
return result


Expand Down Expand Up @@ -203,7 +202,8 @@ class SHA1(HCryptHash):
hash_class = hashlib.sha1


symmetric_algorithms = [RC4, AES128, AES192, AES256]
asymmetric_algorithms = [RSA_KEYX]
hash_algorithms = [MD5, SHA1]
algorithm_registry = dict((x.alg_id, x) for x in symmetric_algorithms + asymmetric_algorithms + hash_algorithms)
algorithm_list = [RC4, AES128, AES192, AES256, RSA_KEYX, MD5, SHA1]
symmetric_algorithms = [x for x in algorithm_list if GET_ALG_CLASS(x.alg_id) == ALG_CLASS_DATA_ENCRYPT]
asymmetric_algorithms = [x for x in algorithm_list if GET_ALG_CLASS(x.alg_id) == ALG_CLASS_KEY_EXCHANGE]
hash_algorithms = [x for x in algorithm_list if GET_ALG_CLASS(x.alg_id) == ALG_CLASS_HASH]
algorithm_registry = dict((x.alg_id, x) for x in algorithm_list)
43 changes: 23 additions & 20 deletions wincrypto/api.py
@@ -1,37 +1,40 @@
from wincrypto import definitions
from wincrypto import constants
from wincrypto.algorithms import algorithm_registry
from wincrypto.definitions import PUBLICKEYSTRUC, PUBLICKEYSTRUC_s, CUR_BLOB_VERSION, bType_PUBLICKEYBLOB, \
from wincrypto.constants import PUBLICKEYSTRUC, PUBLICKEYSTRUC_s, CUR_BLOB_VERSION, bType_PUBLICKEYBLOB, \
bType_PRIVATEKEYBLOB, bType_PLAINTEXTKEYBLOB, bType_SIMPLEBLOB, KP_KEYLEN, KP_ALGID, CALG_AES_192, CALG_AES_256, \
CALG_AES_128
from wincrypto.util import derive_key_3des_aes
CALG_AES_128, ALG_CLASS_KEY_EXCHANGE, ALG_CLASS_DATA_ENCRYPT
from wincrypto.util import derive_key_3des_aes, GET_ALG_CLASS


def CryptImportKey(data, pub_key=None):
publickeystruc = PUBLICKEYSTRUC._make(PUBLICKEYSTRUC_s.unpack_from(data))
if publickeystruc.bVersion != CUR_BLOB_VERSION:
raise NotImplementedError('PUBLICKEYSTRUC.bVersion={} not implemented'.format(publickeystruc.bVersion))

if publickeystruc.aiKeyAlg not in algorithm_registry:
raise NotImplementedError('ALG_ID {:x} not implemented'.format(publickeystruc.aiKeyAlg))

if publickeystruc.bType == bType_PUBLICKEYBLOB:
if publickeystruc.aiKeyAlg not in algorithm_registry:
raise NotImplementedError('ALG_ID {:x} not implemented'.format(publickeystruc.aiKeyAlg))
if GET_ALG_CLASS(publickeystruc.aiKeyAlg) != ALG_CLASS_KEY_EXCHANGE:
raise ValueError('Invalid ALG_ID {:x} for PUBLICKEYBLOB'.format(publickeystruc.aiKeyAlg))
return algorithm_registry[publickeystruc.aiKeyAlg].import_publickeyblob(data[8:])

if publickeystruc.bType == bType_PRIVATEKEYBLOB:
if publickeystruc.aiKeyAlg not in algorithm_registry:
raise NotImplementedError('ALG_ID {:x} not implemented'.format(publickeystruc.aiKeyAlg))
elif publickeystruc.bType == bType_PRIVATEKEYBLOB:
if GET_ALG_CLASS(publickeystruc.aiKeyAlg) != ALG_CLASS_KEY_EXCHANGE:
raise ValueError('Invalid ALG_ID {:x} for PRIVATEKEYBLOB'.format(publickeystruc.aiKeyAlg))
return algorithm_registry[publickeystruc.aiKeyAlg].import_privatekeyblob(data[8:])

if publickeystruc.bType == bType_PLAINTEXTKEYBLOB:
if publickeystruc.aiKeyAlg not in algorithm_registry:
raise NotImplementedError('ALG_ID {:x} not implemented'.format(publickeystruc.aiKeyAlg))
elif publickeystruc.bType == bType_PLAINTEXTKEYBLOB:
if GET_ALG_CLASS(publickeystruc.aiKeyAlg) != ALG_CLASS_DATA_ENCRYPT:
raise ValueError('Invalid ALG_ID {:x} for PLAINTEXTKEYBLOB'.format(publickeystruc.aiKeyAlg))
return algorithm_registry[publickeystruc.aiKeyAlg].import_plaintextkeyblob(data[8:])

if publickeystruc.bType == bType_SIMPLEBLOB:
if publickeystruc.aiKeyAlg not in algorithm_registry:
raise NotImplementedError('ALG_ID {:x} not implemented'.format(publickeystruc.aiKeyAlg))
elif publickeystruc.bType == bType_SIMPLEBLOB:
if GET_ALG_CLASS(publickeystruc.aiKeyAlg) != ALG_CLASS_DATA_ENCRYPT:
raise ValueError('Invalid ALG_ID {:x} for SIMPLEBLOB'.format(publickeystruc.aiKeyAlg))
return algorithm_registry[publickeystruc.aiKeyAlg].import_simpleblob(data[8:], pub_key)

raise NotImplementedError('PUBLICKEYSTRUC.bType={} not implemented'.format(publickeystruc.bType))
else:
raise NotImplementedError('PUBLICKEYSTRUC.bType={} not implemented'.format(publickeystruc.bType))


def CryptDecrypt(crypt_key, encrypted_data):
Expand Down Expand Up @@ -76,11 +79,11 @@ def CryptHashData(hash_alg, data):


def CryptGetHashParam(hash_alg, dwParam):
if dwParam == definitions.HP_ALGID:
if dwParam == constants.HP_ALGID:
return hash_alg.alg_id
elif dwParam == definitions.HP_HASHVAL:
elif dwParam == constants.HP_HASHVAL:
return hash_alg.get_hash_val()
elif dwParam == definitions.HP_HASHSIZE:
elif dwParam == constants.HP_HASHSIZE:
return hash_alg.get_hash_size()
else:
return NotImplementedError('hash param {} not implemented'.format(dwParam))
Expand Down
6 changes: 5 additions & 1 deletion wincrypto/definitions.py → wincrypto/constants.py
Expand Up @@ -41,4 +41,8 @@

CRYPT_EXPORTABLE = 1

CUR_BLOB_VERSION = 2
CUR_BLOB_VERSION = 2

ALG_CLASS_DATA_ENCRYPT = 3 << 13
ALG_CLASS_HASH = 4 << 13
ALG_CLASS_KEY_EXCHANGE = 5 << 13
2 changes: 1 addition & 1 deletion wincrypto/native.py
Expand Up @@ -2,7 +2,7 @@
from ctypes import windll, c_void_p, byref, create_string_buffer, c_int
import struct

from wincrypto.definitions import HP_ALGID, HP_HASHSIZE, KP_KEYLEN, KP_ALGID, CRYPT_EXPORTABLE
from wincrypto.constants import HP_ALGID, HP_HASHSIZE, KP_KEYLEN, KP_ALGID, CRYPT_EXPORTABLE


PROV_RSA_FULL = 1
Expand Down
6 changes: 5 additions & 1 deletion wincrypto/util.py
Expand Up @@ -27,4 +27,8 @@ def derive_key_3des_aes(hash_alg):
hash2 = CryptCreateHash(hash_alg.alg_id)
hash2.hash_data(str(buf2))
derived_key = hash1.get_hash_val() + hash2.get_hash_val()
return derived_key
return derived_key


def GET_ALG_CLASS(x):
return x & (7 << 13)

0 comments on commit 841bd33

Please sign in to comment.