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']
class HMAC:
class HMAC(object):
"""An HMAC hash object.
Do not instantiate directly. Use the :func:`new` function.

View file

@ -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")

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.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

View file

@ -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):

View file

@ -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"]),

View file

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