First version of bcrypt

This commit is contained in:
Helder Eijs 2019-08-14 10:43:05 +02:00
parent c33fcd2c66
commit ca975e96ad
8 changed files with 451 additions and 23 deletions

View file

@ -0,0 +1,122 @@
# ===================================================================
#
# Copyright (c) 2019, Legrandin <helderijs@gmail.com>
# 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)

View file

@ -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]

View file

@ -43,7 +43,7 @@ from Crypto.Random import get_random_bytes
__all__ = ['new', 'HMAC'] __all__ = ['new', 'HMAC']
class HMAC: class HMAC(object):
"""An HMAC hash object. """An HMAC hash object.
Do not instantiate directly. Use the :func:`new` function. Do not instantiate directly. Use the :func:`new` function.

View file

@ -21,13 +21,16 @@
# SOFTWARE. # SOFTWARE.
# =================================================================== # ===================================================================
import re
import struct import struct
from functools import reduce 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.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.number import size as bit_size, long_to_bytes, bytes_to_long
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, 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]) return list(kol[:num_keys])
def scrypt(password, salt, key_len, N, r, p, num_keys=1): def scrypt(password, salt, key_len, N, r, p, num_keys=1):
"""Derive one or more keys from a passphrase. """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] kol = [dk[idx:idx + key_len]
for idx in iter_range(0, key_len * num_keys, key_len)] for idx in iter_range(0, key_len * num_keys, key_len)]
return kol 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")

View file

@ -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.Hash import SHA1, HMAC, SHA256, MD5, SHA224, SHA384, SHA512
from Crypto.Cipher import AES, DES3 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): def t2b(t):
@ -441,6 +444,30 @@ class scrypt_Tests(unittest.TestCase):
self.assertEqual((ref[:4], ref[4:8], ref[8:]), (key1, key2, key3)) 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={}): def get_tests(config={}):
if not config.get('slow_tests'): if not config.get('slow_tests'):
@ -453,6 +480,7 @@ def get_tests(config={}):
tests += list_test_cases(S2V_Tests) tests += list_test_cases(S2V_Tests)
tests += list_test_cases(HKDF_Tests) tests += list_test_cases(HKDF_Tests)
tests += list_test_cases(scrypt_Tests) tests += list_test_cases(scrypt_Tests)
tests += list_test_cases(bcrypt_Tests)
return tests return tests

View file

@ -71,9 +71,9 @@ if sys.version_info[0] == 2:
return str(s) return str(s)
def bord(s): def bord(s):
return ord(s) return ord(s)
def tobytes(s): def tobytes(s, encoding="latin-1"):
if isinstance(s, unicode): if isinstance(s, unicode):
return s.encode("latin-1") return s.encode(encoding)
else: else:
return ''.join(s) return ''.join(s)
def tostr(bs): def tostr(bs):
@ -114,12 +114,12 @@ else:
return bytes(s) return bytes(s)
def bord(s): def bord(s):
return s return s
def tobytes(s): def tobytes(s, encoding="latin-1"):
if isinstance(s,bytes): if isinstance(s,bytes):
return s return s
else: else:
if isinstance(s,str): if isinstance(s,str):
return s.encode("latin-1") return s.encode(encoding)
else: else:
return bytes([s]) return bytes([s])
def tostr(bs): def tostr(bs):

View file

@ -356,6 +356,10 @@ ext_modules = [
Extension("Crypto.Cipher._raw_blowfish", Extension("Crypto.Cipher._raw_blowfish",
include_dirs=['src/'], include_dirs=['src/'],
sources=["src/blowfish.c"]), 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", Extension("Crypto.Cipher._raw_cast",
include_dirs=['src/'], include_dirs=['src/'],
sources=["src/CAST.c"]), sources=["src/CAST.c"]),

View file

@ -37,7 +37,13 @@
FAKE_INIT(raw_blowfish) FAKE_INIT(raw_blowfish)
#ifdef EKS
#define NON_STANDARD_START_OPERATION
#define MODULE_NAME EKSBlowfish
#else
#define MODULE_NAME Blowfish #define MODULE_NAME Blowfish
#endif
#define BLOCK_SIZE 8 #define BLOCK_SIZE 8
#define KEY_SIZE 0 #define KEY_SIZE 0
@ -116,14 +122,12 @@ static void bf_decrypt(const struct block_state *state, uint32_t *Lx, uint32_t *
*Rx = R; *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]; uint8_t P_buf[4*18];
size_t P_idx; size_t P_idx;
unsigned i; unsigned i;
assert(keylength > 0);
P_idx = 0; P_idx = 0;
while (P_idx < sizeof(P_buf)) { while (P_idx < sizeof(P_buf)) {
size_t tc; 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[i] ^= LOAD_U32_BIG(P_buf + P_idx);
P_idx += 4; 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; unsigned i, j;
uint32_t L, R; uint32_t L, R;
/* Allowed key length: 32 to 448 bits */ xorP(state->P, key, keylength);
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);
L = R = 0; L = R = 0;
for (i=0; i<18; i+=2) { 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; 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; 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) 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" #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