diff --git a/lib/Crypto/Cipher/_EKSBlowfish.py b/lib/Crypto/Cipher/_EKSBlowfish.py new file mode 100644 index 00000000..721c3046 --- /dev/null +++ b/lib/Crypto/Cipher/_EKSBlowfish.py @@ -0,0 +1,122 @@ +# =================================================================== +# +# Copyright (c) 2019, Legrandin +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# =================================================================== + +import sys + +from Crypto.Cipher import _create_cipher +from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, + VoidPointer, SmartPointer, c_size_t, + c_uint8_ptr, c_uint) + +_raw_blowfish_lib = load_pycryptodome_raw_lib( + "Crypto.Cipher._raw_eksblowfish", + """ + int EKSBlowfish_start_operation(const uint8_t key[], + size_t key_len, + const uint8_t salt[16], + unsigned cost, + void **pResult); + int EKSBlowfish_encrypt(const void *state, + const uint8_t *in, + uint8_t *out, + size_t data_len); + int EKSBlowfish_decrypt(const void *state, + const uint8_t *in, + uint8_t *out, + size_t data_len); + int EKSBlowfish_stop_operation(void *state); + """ + ) + + +def _create_base_cipher(dict_parameters): + """This method instantiates and returns a smart pointer to + a low-level base cipher. It will absorb named parameters in + the process.""" + + try: + key = dict_parameters.pop("key") + salt = dict_parameters.pop("salt") + cost = dict_parameters.pop("cost") + except KeyError as e: + raise TypeError("Missing EKSBlowfish parameter: " + str(e)) + + if len(key) not in key_size: + raise ValueError("Incorrect EKSBlowfish key length (%d bytes)" % len(key)) + + if len(salt) != 16: + raise ValueError("Incorrect salt length (%d bytes)" % len(salt)) + + start_operation = _raw_blowfish_lib.EKSBlowfish_start_operation + stop_operation = _raw_blowfish_lib.EKSBlowfish_stop_operation + + void_p = VoidPointer() + result = start_operation(c_uint8_ptr(key), + c_size_t(len(key)), + c_uint8_ptr(salt), + c_uint(cost), + void_p.address_of()) + if result: + raise ValueError("Error %X while instantiating the EKSBlowfish cipher" + % result) + return SmartPointer(void_p.get(), stop_operation) + + +def new(key, mode, salt, cost): + """Create a new EKSBlowfish cipher + + Args: + + key (bytes, bytearray, memoryview): + The secret key to use in the symmetric cipher. + Its length can vary from 5 to 56 bytes. + + mode (one of the supported ``MODE_*`` constants): + The chaining mode to use for encryption or decryption. + + salt (bytes, bytearray, memoryview): + The 16 byte salt that bcrypt uses to thwart rainbow table attacks + + cost (integer): + The complexity factor in bcrypt + + :Return: an EKSBlowfish object + """ + + kwargs = { 'salt':salt, 'cost':cost } + return _create_cipher(sys.modules[__name__], key, mode, **kwargs) + + +MODE_ECB = 1 + +# Size of a data block (in bytes) +block_size = 8 +# Size of a key (in bytes) +key_size = range(4, 56 + 1) diff --git a/lib/Crypto/Cipher/_EKSBlowfish.pyi b/lib/Crypto/Cipher/_EKSBlowfish.pyi new file mode 100644 index 00000000..95db3794 --- /dev/null +++ b/lib/Crypto/Cipher/_EKSBlowfish.pyi @@ -0,0 +1,15 @@ +from typing import Union, Iterable + +from Crypto.Cipher._mode_ecb import EcbMode + +MODE_ECB: int + +Buffer = Union[bytes, bytearray, memoryview] + +def new(key: Buffer, + mode: int, + salt: Buffer, + cost: int) -> EcbMode: ... + +block_size: int +key_size: Iterable[int] diff --git a/lib/Crypto/Hash/HMAC.py b/lib/Crypto/Hash/HMAC.py index 78bed484..dd13b430 100644 --- a/lib/Crypto/Hash/HMAC.py +++ b/lib/Crypto/Hash/HMAC.py @@ -43,7 +43,7 @@ from Crypto.Random import get_random_bytes __all__ = ['new', 'HMAC'] -class HMAC: +class HMAC(object): """An HMAC hash object. Do not instantiate directly. Use the :func:`new` function. diff --git a/lib/Crypto/Protocol/KDF.py b/lib/Crypto/Protocol/KDF.py index 9c516f8b..6921dd11 100644 --- a/lib/Crypto/Protocol/KDF.py +++ b/lib/Crypto/Protocol/KDF.py @@ -21,13 +21,16 @@ # SOFTWARE. # =================================================================== +import re import struct from functools import reduce -from Crypto.Util.py3compat import tobytes, bord, _copy_bytes, iter_range +from Crypto.Util.py3compat import (tobytes, bord, _copy_bytes, iter_range, + tostr, bchr, bstr) -from Crypto.Hash import SHA1, SHA256, HMAC, CMAC +from Crypto.Hash import SHA1, SHA256, HMAC, CMAC, BLAKE2s from Crypto.Util.strxor import strxor +from Crypto.Random import get_random_bytes from Crypto.Util.number import size as bit_size, long_to_bytes, bytes_to_long from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, @@ -328,6 +331,7 @@ def HKDF(master, key_len, salt, hashmod, num_keys=1, context=None): return list(kol[:num_keys]) + def scrypt(password, salt, key_len, N, r, p, num_keys=1): """Derive one or more keys from a passphrase. @@ -415,3 +419,153 @@ def scrypt(password, salt, key_len, N, r, p, num_keys=1): kol = [dk[idx:idx + key_len] for idx in iter_range(0, key_len * num_keys, key_len)] return kol + + +def _bcrypt_encode(data): + s = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + + bits = [] + for c in data: + bits_c = bin(bord(c))[2:].zfill(8) + bits.append(bstr(bits_c)) + bits = b"".join(bits) + + bits6 = [ bits[idx:idx+6] for idx in range(0, len(bits), 6) ] + + result = [] + for g in bits6[:-1]: + idx = int(g, 2) + result.append(s[idx]) + + g = bits6[-1] + idx = int(g, 2) << (6 - len(g)) + result.append(s[idx]) + result = "".join(result) + + return tobytes(result) + + +def _bcrypt_decode(data): + s = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + + bits = [] + for c in tostr(data): + idx = s.find(c) + bits6 = bin(idx)[2:].zfill(6) + bits.append(bits6) + bits = "".join(bits) + + modulo4 = len(data) % 4 + if modulo4 == 1: + raise ValueError("Incorrect length") + elif modulo4 == 2: + bits = bits[:-4] + elif modulo4 == 3: + bits = bits[:-2] + + bits8 = [ bits[idx:idx+8] for idx in range(0, len(bits), 8) ] + + result = [] + for g in bits8: + result.append(bchr(int(g, 2))) + result = b"".join(result) + + return result + + +def bcrypt(password, cost, salt=None): + """Hash a password into a key, using the OpenBSD bcrypt protocol. + + Args: + password (byte string or string): + The secret password or pass phrase. + It must be at most 72 bytes long. + Unicode strings will be encoded as UTF-8. + cost (integer): + The exponential factor that makes it slower to compute the hash. + It must be in the range 4 to 31. + salt (byte string): + Optional. Random byte string to thwarts dictionary and rainbow table + attacks. It must be 16 bytes long. + If not passed, a random value is generated. + + Return (byte string): + The bcrypt hash + """ + + from Crypto.Cipher import _EKSBlowfish + + password = tobytes(password, "utf-8") + + if len(password) < 72: + password += b"\x00" + if len(password) > 72: + raise ValueError("The password is too long. It must be 72 bytes at most.") + + if salt is None: + salt = get_random_bytes(16) + + if len(salt) != 16: + raise ValueError("bcrypt salt must be 16 bytes long") + + if not (4 <= cost <= 31): + raise ValueError("bcrypt cost factor must be in the range 4..31") + + cipher = _EKSBlowfish.new(password, _EKSBlowfish.MODE_ECB, salt, cost) + ctext = b"OrpheanBeholderScryDoubt" + for _ in range(64): + ctext = cipher.encrypt(ctext) + + cost_enc = b"$" + bstr(str(cost).zfill(2)) + salt_enc = b"$" + _bcrypt_encode(salt) + + # Only use 23 bytes, not 24 + hash_enc = _bcrypt_encode(ctext[:-1]) + + result = b"$2a" + cost_enc + salt_enc + hash_enc + + return result + + +def bcrypt_check(password, bcrypt_hash): + """Verify if the provided password matches the given bcrypt hash. + + Args: + password (byte string or string): + The secret password or pass phrase to test. + It must be at most 72 bytes long. + Unicode strings will be encoded as UTF-8. + bcrypt_hash (byte string): + The reference bcrypt hash the password needs to be checked against. + + Raises: + ValueError: if password is invalid + """ + + bcrypt_hash = tobytes(bcrypt_hash) + + if len(bcrypt_hash) != 60: + raise ValueError("Incorrect length of the bcrypt hash: %d bytes instead of 60" % len(bcrypt_hash)) + + if bcrypt_hash[:4] != b'$2a$': + raise ValueError("Unsupported prefix") + + p = re.compile(b'\$2a\$([0-9][0-9])\$([A-Za-z0-9./]{22,22})([A-Za-z0-9./]{31,31})') + r = p.match(bcrypt_hash) + if not r: + raise ValueError("Incorrect bcrypt hash format") + + cost = int(r.group(1)) + if not (4 <= cost <= 31): + raise ValueError("Incorrect cost") + + salt = _bcrypt_decode(r.group(2)) + + bcrypt_hash2 = bcrypt(password, cost, salt) + + secret = get_random_bytes(16) + + mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=bcrypt_hash).digest() + mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=bcrypt_hash2).digest() + if mac1 != mac2: + raise ValueError("Incorrect bcrypt hash") diff --git a/lib/Crypto/SelfTest/Protocol/test_KDF.py b/lib/Crypto/SelfTest/Protocol/test_KDF.py index 9fd6baa6..e1a3dc90 100644 --- a/lib/Crypto/SelfTest/Protocol/test_KDF.py +++ b/lib/Crypto/SelfTest/Protocol/test_KDF.py @@ -29,7 +29,10 @@ from Crypto.SelfTest.st_common import list_test_cases from Crypto.Hash import SHA1, HMAC, SHA256, MD5, SHA224, SHA384, SHA512 from Crypto.Cipher import AES, DES3 -from Crypto.Protocol.KDF import PBKDF1, PBKDF2, _S2V, HKDF, scrypt +from Crypto.Protocol.KDF import (PBKDF1, PBKDF2, _S2V, HKDF, scrypt, + bcrypt, bcrypt_check) + +from Crypto.Protocol.KDF import _bcrypt_decode def t2b(t): @@ -441,6 +444,30 @@ class scrypt_Tests(unittest.TestCase): self.assertEqual((ref[:4], ref[4:8], ref[8:]), (key1, key2, key3)) +class bcrypt_Tests(unittest.TestCase): + + # https://github.com/patrickfav/bcrypt/wiki/Published-Test-Vectors + + def test_random_password_and_salt_short_pw(self): + + # password, cost, salt, bcrypt hash + tvs = [ + (b"<.S.2K(Zq'", 4, b"VYAclAMpaXY/oqAo9yUpku", b"$2a$04$VYAclAMpaXY/oqAo9yUpkuWmoYywaPzyhu56HxXpVltnBIfmO9tgu"), + (b"5.rApO%5jA", 5, b"kVNDrnYKvbNr5AIcxNzeIu", b"$2a$05$kVNDrnYKvbNr5AIcxNzeIuRcyIF5cZk6UrwHGxENbxP5dVv.WQM/G"), + (b"oW++kSrQW^", 6, b"QLKkRMH9Am6irtPeSKN5sO", b"$2a$06$QLKkRMH9Am6irtPeSKN5sObJGr3j47cO6Pdf5JZ0AsJXuze0IbsNm"), + (b"ggJ\\KbTnDG", 7, b"4H896R09bzjhapgCPS/LYu", b"$2a$07$4H896R09bzjhapgCPS/LYuMzAQluVgR5iu/ALF8L8Aln6lzzYXwbq"), + (b"49b0:;VkH/", 8, b"hfvO2retKrSrx5f2RXikWe", b"$2a$08$hfvO2retKrSrx5f2RXikWeFWdtSesPlbj08t/uXxCeZoHRWDz/xFe"), + (b">9N^5jc##'", 9, b"XZLvl7rMB3EvM0c1.JHivu", b"$2a$09$XZLvl7rMB3EvM0c1.JHivuIDPJWeNJPTVrpjZIEVRYYB/mF6cYgJK"), + (b"\\$ch)s4WXp", 10, b"aIjpMOLK5qiS9zjhcHR5TO", b"$2a$10$aIjpMOLK5qiS9zjhcHR5TOU7v2NFDmcsBmSFDt5EHOgp/jeTF3O/q"), + (b"RYoj\\_>2P7", 12, b"esIAHiQAJNNBrsr5V13l7.", b"$2a$12$esIAHiQAJNNBrsr5V13l7.RFWWJI2BZFtQlkFyiWXjou05GyuREZa"), + ] + + for (idx, (password, cost, salt64, result)) in enumerate(tvs): + x = bcrypt(password, cost, salt=_bcrypt_decode(salt64)) + self.assertEqual(x, result) + bcrypt_check(password, result) + + def get_tests(config={}): if not config.get('slow_tests'): @@ -453,6 +480,7 @@ def get_tests(config={}): tests += list_test_cases(S2V_Tests) tests += list_test_cases(HKDF_Tests) tests += list_test_cases(scrypt_Tests) + tests += list_test_cases(bcrypt_Tests) return tests diff --git a/lib/Crypto/Util/py3compat.py b/lib/Crypto/Util/py3compat.py index 4bb80138..f71b0282 100644 --- a/lib/Crypto/Util/py3compat.py +++ b/lib/Crypto/Util/py3compat.py @@ -71,9 +71,9 @@ if sys.version_info[0] == 2: return str(s) def bord(s): return ord(s) - def tobytes(s): + def tobytes(s, encoding="latin-1"): if isinstance(s, unicode): - return s.encode("latin-1") + return s.encode(encoding) else: return ''.join(s) def tostr(bs): @@ -114,12 +114,12 @@ else: return bytes(s) def bord(s): return s - def tobytes(s): + def tobytes(s, encoding="latin-1"): if isinstance(s,bytes): return s else: if isinstance(s,str): - return s.encode("latin-1") + return s.encode(encoding) else: return bytes([s]) def tostr(bs): diff --git a/setup.py b/setup.py index a8c7f98b..20f66f10 100644 --- a/setup.py +++ b/setup.py @@ -356,6 +356,10 @@ ext_modules = [ Extension("Crypto.Cipher._raw_blowfish", include_dirs=['src/'], sources=["src/blowfish.c"]), + Extension("Crypto.Cipher._raw_eksblowfish", + include_dirs=['src/'], + define_macros=[('EKS',None),], + sources=["src/blowfish.c"]), Extension("Crypto.Cipher._raw_cast", include_dirs=['src/'], sources=["src/CAST.c"]), diff --git a/src/blowfish.c b/src/blowfish.c index 6f8268ed..0fc1b0bd 100644 --- a/src/blowfish.c +++ b/src/blowfish.c @@ -37,7 +37,13 @@ FAKE_INIT(raw_blowfish) +#ifdef EKS +#define NON_STANDARD_START_OPERATION +#define MODULE_NAME EKSBlowfish +#else #define MODULE_NAME Blowfish +#endif + #define BLOCK_SIZE 8 #define KEY_SIZE 0 @@ -116,14 +122,12 @@ static void bf_decrypt(const struct block_state *state, uint32_t *Lx, uint32_t * *Rx = R; } -static int xorkey(uint32_t P[18], const uint8_t *key, size_t keylength) +static inline void xorP(uint32_t P[18], const uint8_t *key, size_t keylength) { uint8_t P_buf[4*18]; size_t P_idx; unsigned i; - assert(keylength > 0); - P_idx = 0; while (P_idx < sizeof(P_buf)) { size_t tc; @@ -139,24 +143,14 @@ static int xorkey(uint32_t P[18], const uint8_t *key, size_t keylength) P[i] ^= LOAD_U32_BIG(P_buf + P_idx); P_idx += 4; } - - return 0; } -static int block_init(struct block_state *state, const uint8_t *key, size_t keylength) +static inline void encryptState(struct block_state *state, const uint8_t *key, size_t keylength) { unsigned i, j; uint32_t L, R; - /* Allowed key length: 32 to 448 bits */ - if (keylength < 4 || keylength > 56) { - return ERR_KEY_SIZE; - } - - memcpy(state->S, S_init, sizeof S_init); - memcpy(state->P, P_init, sizeof P_init); - - xorkey(state->P, key, keylength); + xorP(state->P, key, keylength); L = R = 0; for (i=0; i<18; i+=2) { @@ -171,10 +165,99 @@ static int block_init(struct block_state *state, const uint8_t *key, size_t keyl state->S[j][i+1] = R; } } +} + +#ifndef EKS + +static int block_init(struct block_state *state, const uint8_t *key, size_t keylength) +{ + /* Allowed key length: 32 to 448 bits */ + if (keylength < 4 || keylength > 56) { + return ERR_KEY_SIZE; + } + + memcpy(state->S, S_init, sizeof S_init); + memcpy(state->P, P_init, sizeof P_init); + + encryptState(state, key, keylength); return 0; } +#else + +static void encryptStateWithSalt(struct block_state *state, const uint8_t *key, size_t keylength, const uint8_t salt[16]) +{ + uint32_t S1, S2, S3, S4; + uint32_t L, R; + unsigned i, j; + + xorP(state->P, key, keylength); + + S1 = LOAD_U32_BIG(salt); + S2 = LOAD_U32_BIG(salt+4); + S3 = LOAD_U32_BIG(salt+8); + S4 = LOAD_U32_BIG(salt+12); + + L = R = 0; + for (i=0;;) { + L ^= S1; + R ^= S2; + bf_encrypt(state, &L, &R); + state->P[i++] = L; + state->P[i++] = R; + + if (i == 18) break; + + L ^= S3; + R ^= S4; + bf_encrypt(state, &L, &R); + state->P[i++] = L; + state->P[i++] = R; + } + + for (j=0; j<4; j++) { + for (i=0; i<256;) { + L ^= S3; + R ^= S4; + bf_encrypt(state, &L, &R); + state->S[j][i++] = L; + state->S[j][i++] = R; + + L ^= S1; + R ^= S2; + bf_encrypt(state, &L, &R); + state->S[j][i++] = L; + state->S[j][i++] = R; + } + } +} + +static int block_init(struct block_state *state, const uint8_t *key, size_t keylength, const uint8_t salt[16], unsigned cost) +{ + unsigned i; + + /* Allowed key length: 32 to 448 bits */ + if (keylength < 4 || keylength > 56) { + return ERR_KEY_SIZE; + } + + /* InitState */ + memcpy(state->S, S_init, sizeof S_init); + memcpy(state->P, P_init, sizeof P_init); + + encryptStateWithSalt(state, key, keylength, salt); + + for (i=0; i<(1 << cost); i++) { + encryptState(state, key, keylength); + encryptState(state, salt, 16); + } + + return 0; +} + +#endif + static void block_finalize(struct block_state* state) { } @@ -202,3 +285,25 @@ static inline void block_decrypt(struct block_state *state, const uint8_t *in, u } #include "block_common.c" + +#ifdef EKS +EXPORT_SYM int CIPHER_START_OPERATION(const uint8_t key[], size_t key_len, const uint8_t salt[16], unsigned cost, CIPHER_STATE_TYPE **pResult) +{ + BlockBase *block_base; + + if ((key == NULL) || (pResult == NULL)) + return ERR_NULL; + + *pResult = calloc(1, sizeof(CIPHER_STATE_TYPE)); + if (NULL == *pResult) + return ERR_MEMORY; + + block_base = &((*pResult)->base_state); + block_base->encrypt = &CIPHER_ENCRYPT; + block_base->decrypt = &CIPHER_DECRYPT; + block_base->destructor = &CIPHER_STOP_OPERATION; + block_base->block_len = BLOCK_SIZE; + + return block_init(&(*pResult)->algo_state, (unsigned char*)key, key_len, salt, cost); +} +#endif