Skip to content

Commit

Permalink
Add more hash algorithms for PBES encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
Legrandin committed Jan 6, 2024
1 parent 3246f91 commit 782d014
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 39 deletions.
27 changes: 26 additions & 1 deletion lib/Crypto/Hash/HMAC.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,44 @@

from binascii import unhexlify

from Crypto.Hash import MD5
from Crypto.Hash import BLAKE2s
from Crypto.Util.strxor import strxor
from Crypto.Random import get_random_bytes

__all__ = ['new', 'HMAC']

_hash2hmac_oid = {
'1.3.14.3.2.26': '1.2.840.113549.2.7', # SHA-1
'2.16.840.1.101.3.4.2.4': '1.2.840.113549.2.8', # SHA-224
'2.16.840.1.101.3.4.2.1': '1.2.840.113549.2.9', # SHA-256
'2.16.840.1.101.3.4.2.2': '1.2.840.113549.2.10', # SHA-384
'2.16.840.1.101.3.4.2.3': '1.2.840.113549.2.11', # SHA-512
'2.16.840.1.101.3.4.2.5': '1.2.840.113549.2.12', # SHA-512_224
'2.16.840.1.101.3.4.2.6': '1.2.840.113549.2.13', # SHA-512_256
'2.16.840.1.101.3.4.2.7': '2.16.840.1.101.3.4.2.13', # SHA-3 224
'2.16.840.1.101.3.4.2.8': '2.16.840.1.101.3.4.2.14', # SHA-3 256
'2.16.840.1.101.3.4.2.9': '2.16.840.1.101.3.4.2.15', # SHA-3 384
'2.16.840.1.101.3.4.2.10': '2.16.840.1.101.3.4.2.16', # SHA-3 512
}

_hmac2hash_oid = {v: k for k, v in _hash2hmac_oid.items()}


class HMAC(object):
"""An HMAC hash object.
Do not instantiate directly. Use the :func:`new` function.
:ivar digest_size: the size in bytes of the resulting MAC tag
:vartype digest_size: integer
:ivar oid: the ASN.1 object ID of the HMAC algorithm.
Only present if the algorithm was officially assigned one.
"""

def __init__(self, key, msg=b"", digestmod=None):

if digestmod is None:
from Crypto.Hash import MD5
digestmod = MD5

if msg is None:
Expand All @@ -64,6 +83,12 @@ def __init__(self, key, msg=b"", digestmod=None):

self._digestmod = digestmod

# Hash OID --> HMAC OID
try:
self.oid = _hash2hmac_oid[digestmod.oid]
except (KeyError, AttributeError):
pass

if isinstance(key, memoryview):
key = key.tobytes()

Expand Down
43 changes: 42 additions & 1 deletion lib/Crypto/Hash/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,48 @@
# ===================================================================

__all__ = ['HMAC', 'MD2', 'MD4', 'MD5', 'RIPEMD160', 'SHA1',
'SHA224', 'SHA256', 'SHA384', 'SHA512', 'CMAC', 'Poly1305',
'SHA224', 'SHA256', 'SHA384', 'SHA512',
'SHA3_224', 'SHA3_256', 'SHA3_384', 'SHA3_512',
'CMAC', 'Poly1305',
'cSHAKE128', 'cSHAKE256', 'KMAC128', 'KMAC256',
'TupleHash128', 'TupleHash256', 'KangarooTwelve',
'TurboSHAKE128', 'TurboSHAKE256']

def new(name):
name = name.upper()
if name in ("1.3.14.3.2.26", "SHA1"):
from . import SHA1
return SHA1.new()
if name in ("2.16.840.1.101.3.4.2.4", "SHA224"):
from . import SHA224
return SHA224.new()
if name in ("2.16.840.1.101.3.4.2.1", "SHA256"):
from . import SHA256
return SHA256.new()
if name in ("2.16.840.1.101.3.4.2.2", "SHA384"):
from . import SHA384
return SHA384.new()
if name in ("2.16.840.1.101.3.4.2.3", "SHA512"):
from . import SHA512
return SHA512.new()
if name in ("2.16.840.1.101.3.4.2.5", "SHA512-224"):
from . import SHA512
return SHA512.new(truncate=224)
if name in ("2.16.840.1.101.3.4.2.6", "SHA512-256"):
from . import SHA512
return SHA512.new(truncate=256)
if name in ("2.16.840.1.101.3.4.2.7", "SHA3-224"):
from . import SHA3_224
return SHA3_224.new()
if name in ("2.16.840.1.101.3.4.2.8", "SHA3-256"):
from . import SHA3_256
return SHA3_256.new()
if name in ("2.16.840.1.101.3.4.2.9", "SHA3-384"):
from . import SHA3_384
return SHA3_384.new()
if name in ("2.16.840.1.101.3.4.2.10", "SHA3-512"):
from . import SHA3_512
return SHA3_512.new()
else:
raise ValueError("Unknown hash %s" % str(name))

119 changes: 84 additions & 35 deletions lib/Crypto/IO/_PBES.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================

import re

from Crypto import Hash
from Crypto import Random
from Crypto.Util.asn1 import (
DerSequence, DerOctetString,
DerObjectId, DerInteger,
)

from Crypto.Util.Padding import pad, unpad
from Crypto.Hash import MD5, SHA1, SHA224, SHA256, SHA384, SHA512
from Crypto.Cipher import DES, ARC2, DES3, AES
from Crypto.Protocol.KDF import PBKDF1, PBKDF2, scrypt

_OID_PBE_WITH_MD5_AND_DES_CBC = "1.2.840.113549.1.5.3"
Expand All @@ -53,10 +54,6 @@
_OID_SCRYPT = "1.3.6.1.4.1.11591.4.11"

_OID_HMAC_SHA1 = "1.2.840.113549.2.7"
_OID_HMAC_SHA224 = "1.2.840.113549.2.8"
_OID_HMAC_SHA256 = "1.2.840.113549.2.9"
_OID_HMAC_SHA384 = "1.2.840.113549.2.10"
_OID_HMAC_SHA512 = "1.2.840.113549.2.11"

_OID_DES_EDE3_CBC = "1.2.840.113549.3.7"
_OID_AES128_CBC = "2.16.840.1.101.3.4.1.2"
Expand Down Expand Up @@ -103,6 +100,16 @@ class PbesError(ValueError):
# prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
# }
#
# PBKDF2-PRFs ALGORITHM-IDENTIFIER ::= {
# {NULL IDENTIFIED BY id-hmacWithSHA1},
# {NULL IDENTIFIED BY id-hmacWithSHA224},
# {NULL IDENTIFIED BY id-hmacWithSHA256},
# {NULL IDENTIFIED BY id-hmacWithSHA384},
# {NULL IDENTIFIED BY id-hmacWithSHA512},
# {NULL IDENTIFIED BY id-hmacWithSHA512-224},
# {NULL IDENTIFIED BY id-hmacWithSHA512-256},
# ...
# }
# scrypt-params ::= SEQUENCE {
# salt OCTET STRING,
# costParameter INTEGER (1..MAX),
Expand All @@ -111,6 +118,7 @@ class PbesError(ValueError):
# keyLength INTEGER (1..MAX) OPTIONAL
# }


class PBES1(object):
"""Deprecated encryption scheme with password-based key derivation
(originally defined in PKCS#5 v1.5, but still present in `v2.0`__).
Expand Down Expand Up @@ -141,19 +149,27 @@ def decrypt(data, passphrase):
cipher_params = {}
if pbe_oid == _OID_PBE_WITH_MD5_AND_DES_CBC:
# PBE_MD5_DES_CBC
from Crypto.Hash import MD5
from Crypto.Cipher import DES
hashmod = MD5
ciphermod = DES
elif pbe_oid == _OID_PBE_WITH_MD5_AND_RC2_CBC:
# PBE_MD5_RC2_CBC
from Crypto.Hash import MD5
from Crypto.Cipher import ARC2
hashmod = MD5
ciphermod = ARC2
cipher_params['effective_keylen'] = 64
elif pbe_oid == _OID_PBE_WITH_SHA1_AND_DES_CBC:
# PBE_SHA1_DES_CBC
from Crypto.Hash import SHA1
from Crypto.Cipher import DES
hashmod = SHA1
ciphermod = DES
elif pbe_oid == _OID_PBE_WITH_SHA1_AND_RC2_CBC:
# PBE_SHA1_RC2_CBC
from Crypto.Hash import SHA1
from Crypto.Cipher import ARC2
hashmod = SHA1
ciphermod = ARC2
cipher_params['effective_keylen'] = 64
Expand Down Expand Up @@ -231,48 +247,80 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None):
if randfunc is None:
randfunc = Random.new().read

if protection == 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC':
pattern = re.compile(r'^(PBKDF2WithHMAC-([0-9A-Z-]+)|scrypt)And([0-9A-Z-]+)$')
res = pattern.match(protection)
if res is None:
raise ValueError("Unknown protection %s" % protection)

if protection.startswith("PBKDF"):
pbkdf = "pbkdf2"
pbkdf2_hmac_algo = res.group(2)
enc_algo = res.group(3)
else:
pbkdf = "scrypt"
enc_algo = res.group(3)

if enc_algo == 'DES-EDE3-CBC':
from Crypto.Cipher import DES3
key_size = 24
module = DES3
cipher_mode = DES3.MODE_CBC
enc_oid = _OID_DES_EDE3_CBC
elif protection in ('PBKDF2WithHMAC-SHA1AndAES128-CBC',
'scryptAndAES128-CBC'):
elif enc_algo == 'AES128-CBC':
from Crypto.Cipher import AES
key_size = 16
module = AES
cipher_mode = AES.MODE_CBC
enc_oid = _OID_AES128_CBC
elif protection in ('PBKDF2WithHMAC-SHA1AndAES192-CBC',
'scryptAndAES192-CBC'):
elif enc_algo == 'AES192-CBC':
from Crypto.Cipher import AES
key_size = 24
module = AES
cipher_mode = AES.MODE_CBC
enc_oid = _OID_AES192_CBC
elif protection in ('PBKDF2WithHMAC-SHA1AndAES256-CBC',
'scryptAndAES256-CBC'):
elif enc_algo == 'AES256-CBC':
from Crypto.Cipher import AES
key_size = 32
module = AES
cipher_mode = AES.MODE_CBC
enc_oid = _OID_AES256_CBC
else:
raise ValueError("Unknown PBES2 mode")
raise ValueError("Unknown encryption mode '%s'" % enc_algo)

# Get random data
iv = randfunc(module.block_size)
salt = randfunc(prot_params.get("salt_size", 8))

# Derive key from password
if protection.startswith('PBKDF2'):
if pbkdf == 'pbkdf2':

count = prot_params.get("iteration_count", 1000)
key = PBKDF2(passphrase, salt, key_size, count)
digestmod = Hash.new(pbkdf2_hmac_algo)

key = PBKDF2(passphrase,
salt,
key_size,
count,
hmac_hash_module=digestmod)

pbkdf2_params = DerSequence([
DerOctetString(salt),
DerInteger(count)
])

if pbkdf2_hmac_algo != 'SHA1':
try:
hmac_oid = Hash.HMAC.new(digestmod).oid
except KeyError:
raise ValueError("No OID for HMAC hash algorithm")
pbkdf2_params.append(DerObjectId(hmac_oid))

kdf_info = DerSequence([
DerObjectId(_OID_PBKDF2), # PBKDF2
DerSequence([
DerOctetString(salt),
DerInteger(count)
])
pbkdf2_params
])
else:

elif pbkdf == 'scrypt':
# It must be scrypt
count = prot_params.get("iteration_count", 16384)
scrypt_r = prot_params.get('block_size', 8)
Expand All @@ -289,6 +337,9 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None):
])
])

else:
raise ValueError("Unknown KDF " + res.group(1))

# Create cipher and use it
cipher = module.new(key, cipher_mode, iv)
encrypted_data = cipher.encrypt(pad(data, cipher.block_size))
Expand Down Expand Up @@ -336,7 +387,7 @@ def decrypt(data, passphrase):

pbes2_params = DerSequence().decode(enc_algo[1], nr_elements=2)

### Key Derivation Function selection
# Key Derivation Function selection
kdf_info = DerSequence().decode(pbes2_params[0], nr_elements=2)
kdf_oid = DerObjectId().decode(kdf_info[0]).value

Expand All @@ -361,7 +412,7 @@ def decrypt(data, passphrase):
pass

# Default is HMAC-SHA1
pbkdf2_prf_oid = "1.2.840.113549.2.7"
pbkdf2_prf_oid = _OID_HMAC_SHA1
if left > 0:
pbkdf2_prf_algo_id = DerSequence().decode(pbkdf2_params[idx])
pbkdf2_prf_oid = DerObjectId().decode(pbkdf2_prf_algo_id[0]).value
Expand All @@ -379,24 +430,28 @@ def decrypt(data, passphrase):
else:
raise PbesError("Unsupported PBES2 KDF")

### Cipher selection
# Cipher selection
enc_info = DerSequence().decode(pbes2_params[1])
enc_oid = DerObjectId().decode(enc_info[0]).value

if enc_oid == _OID_DES_EDE3_CBC:
# DES_EDE3_CBC
from Crypto.Cipher import DES3
ciphermod = DES3
key_size = 24
elif enc_oid == _OID_AES128_CBC:
# AES128_CBC
from Crypto.Cipher import AES
ciphermod = AES
key_size = 16
elif enc_oid == _OID_AES192_CBC:
# AES192_CBC
from Crypto.Cipher import AES
ciphermod = AES
key_size = 24
elif enc_oid == _OID_AES256_CBC:
# AES256_CBC
from Crypto.Cipher import AES
ciphermod = AES
key_size = 32
else:
Expand All @@ -410,18 +465,12 @@ def decrypt(data, passphrase):

# Create cipher
if kdf_oid == _OID_PBKDF2:
if pbkdf2_prf_oid == _OID_HMAC_SHA1:
hmac_hash_module = SHA1
elif pbkdf2_prf_oid == _OID_HMAC_SHA224:
hmac_hash_module = SHA224
elif pbkdf2_prf_oid == _OID_HMAC_SHA256:
hmac_hash_module = SHA256
elif pbkdf2_prf_oid == _OID_HMAC_SHA384:
hmac_hash_module = SHA384
elif pbkdf2_prf_oid == _OID_HMAC_SHA512:
hmac_hash_module = SHA512
else:

try:
hmac_hash_module_oid = Hash.HMAC._hmac2hash_oid[pbkdf2_prf_oid]
except KeyError:
raise PbesError("Unsupported HMAC %s" % pbkdf2_prf_oid)
hmac_hash_module = Hash.new(hmac_hash_module_oid)

key = PBKDF2(passphrase, salt, key_size, iteration_count,
hmac_hash_module=hmac_hash_module)
Expand Down
3 changes: 2 additions & 1 deletion lib/Crypto/PublicKey/RSA.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def __str__(self):
return "%s RSA key at 0x%X" % (key_type, id(self))

def export_key(self, format='PEM', passphrase=None, pkcs=1,
protection=None, randfunc=None):
protection=None, randfunc=None, prot_params=None):
"""Export this RSA key.
Args:
Expand Down Expand Up @@ -383,6 +383,7 @@ def export_key(self, format='PEM', passphrase=None, pkcs=1,
protection = 'PBKDF2WithHMAC-SHA1AndDES-EDE3-CBC'
binary_key = PKCS8.wrap(binary_key, oid,
passphrase, protection,
prot_params=prot_params,
key_params=DerNull())
passphrase = None
else:
Expand Down
2 changes: 1 addition & 1 deletion lib/Crypto/SelfTest/IO/test_PKCS8.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def test3(self):
"""Verify unwrapping with encryption"""

for t in self.wrapped_enc_keys:
res1, res2, res3 = PKCS8.unwrap(t[4], b("TestTest"))
res1, res2, res3 = PKCS8.unwrap(t[4], b"TestTest")
self.assertEqual(res1, self.oid_key)
self.assertEqual(res2, self.clear_key)

Expand Down

0 comments on commit 782d014

Please sign in to comment.