Scrypt
Scrypt
- resistant to dictionary attacks, GPU attacks and ASIC attacks
- Uses a lot of memory biased on the options
- Memory required = 128 * N * r * p bytes
- Uses the Salsa key schedule algorithm as a Mixer
- Uses single rounds of pbkdf2-sha256 to initialize the internal buffer
- Options
- N – iterations count (affects memory and CPU usage), e.g. 16384 or 2048
- r – block size (affects memory and CPU usage), e.g. 8
- p – parallelism factor (threads to run in parallel - affects the memory, CPU usage), usually 1
- password – the input password (8-10 chars minimal length is recommended)
- salt – securely-generated random bytes (64 bits minimum, 128 bits recommended)
- derived-key-length - how many bytes to generate as output, e.g. 32 bytes (256 bits)
Usage
import hashlib, base64
#Scrypt(password, salt, itteration_count, block_size, parallel, output_size)
password = b'p@$Sw0rD~7'
salt = b'aa1f2d3f4d23ac44e9c5a6c3d8f9ee8c'
itteration_count = 2048
block_size = 8
parallel = 1
output_size = 32
key = hashlib.scrypt(password, salt=salt, n=itteration_count, r=block_size, p=parallel, dklen=output_size)
print("Derived key:", key.hex())
#Derived key: e813a6f6ccc4e9110193bf9efb7c0a489d76655f9e36629dccbeaf2a73bc0c6f
print("{}${}${}${}${}".format(itteration_count, block_size, parallel, base64.b64encode(salt).decode('utf-8'), base64.b64encode(key).decode('utf-8')))
Implementation
from cryptopals_lib import *
from pbkdf2 import pbkdf2
from chacha import salsa_key_schedule
import os, hashlib
class Scrypt():
"""docstring for Scrypt"""
def __init__(self, itterations=16384, memory_cost=8, parallel_cost=1, keylength=64):
#Check that memory factor * parallel factor is not greater than 2^30
if memory_cost * parallel_cost > 2 ** 30:
raise Exception("Too much memory in use.")
self.memory_cost = memory_cost
self.parallel_cost = parallel_cost
#Check if Itterations is not a power of 2 greater than 2^0
if itterations < 2 or (itterations & (itterations -1)):
raise Exception("itterations not a power of 2.")
self.itterations = itterations
self.keylength = keylength
#Initalize other temp buffers
self.memory_buffer = [ 0 ] * (self.memory_cost << 6)
self.itter_buffer = [ 0 ] * ((self.memory_cost * self.itterations) << 5)
def _smix(self, buffer, round_num):
index_from = (round_num * self.memory_cost) << 5
block_size = (self.memory_cost<< 5)
#Populate the Memory buffer from the input buffer
self.memory_buffer[:block_size] = buffer[index_from: index_from + block_size]
#print(self.memory_buffer)
#Mix the memory buffer and update the itteration buffer
for i in range(self.itterations):
index_to = i * block_size
self.itter_buffer[index_to:index_to + block_size] = self.memory_buffer[:block_size]
#Do a Block mix
self.memory_buffer = self._block_mix(self.memory_buffer)
#
for i in range(self.itterations):
start_index = self.memory_buffer[(2 * self.memory_cost -1) << 4] & (self.itterations -1)
for j in range(block_size):
self.memory_buffer[j] ^= self.itter_buffer[(start_index * block_size) + j]
#Do a Block mix
self.memory_buffer = self._block_mix(self.memory_buffer)
#print(self.memory_buffer)
buffer[index_from:index_from + block_size] = self.memory_buffer[:block_size]
return buffer
def _block_mix(self, buffer):
start_index = (2 * self.memory_cost - 1) << 4
temp = buffer[start_index:start_index+16]
for i in range(2*self.memory_cost):
for j in range(16):
temp[j] ^= buffer[(i <<4) + j]
#Salsa round
temp = salsa_key_schedule(temp, rounds=8)
#Choose to replace in buffer
index_to = (self.memory_cost << 5) + (i << 4)
buffer[index_to:index_to+16] = temp[:16]
#Copy Blocks arround
for i in range(self.memory_cost):
index_from = (self.memory_cost + i) << 5
index_to = i << 4
buffer[index_to:index_to + 16] = buffer[index_from:index_from + 16]
for i in range(self.memory_cost):
index_from = ((self.memory_cost + i) << 5) + 16
index_to = (self.memory_cost + i) << 4
buffer[index_to:index_to + 16] = buffer[index_from:index_from + 16]
return buffer
def hash(self, password, salt=None):
self.password = password
if salt == None:
self.salt = os.urandom(16)
else:
self.salt = salt
#???
buffer = pbkdf2(self.password, self.salt, itterations=1, keylength=((self.parallel_cost*self.memory_cost)<< 7), hashobj=hashlib.sha256)
int_buffer = bytes_to_intarray(buffer, 4, byte_order="little")
#smix rounds
for round_idx in range(self.parallel_cost):
int_buffer = self._smix(int_buffer, round_num=round_idx)
buffer = intarray_to_bytes(int_buffer, 4, byte_order="little")
return pbkdf2(self.password, buffer, itterations=1, keylength=self.keylength, hashobj=hashlib.sha256)
if __name__ == '__main__':
test = Scrypt(itterations=1024, memory_cost=1, parallel_cost=1, keylength=64)
out = test.hash(password = b"correct horse battery staple", salt = b"seasalt")
print(out.hex())
#8dc98cddcf52dd725d52b913f7bf8386fa44e1406795aa661487f434007dff1680be6baddd724659316f7ff4663174a7a4ead1c95d5175cf284ac9ae8703e1fb
test = Scrypt(itterations=1024, memory_cost=2, parallel_cost=1, keylength=64)
out = test.hash(password = b"correct horse battery staple", salt = b"seasalt")
print(out.hex())
#77053f0f354002c0f2a240ce9c7b17625fb1440f87a714451217e901f7d03d748411b0bc8e4c150a573f40b98dfa816cf12bb6b01a7567970f1448d7d2a1367a
test = Scrypt(itterations=1024, memory_cost=1, parallel_cost=2, keylength=64)
out = test.hash(password = b"correct horse battery staple", salt = b"seasalt")
print(out.hex())
#cc93bb017c38aaf54901146bdc5d21c21be2314ea63ec0a4466ea44af50c8a5c87b5cd567b8205f69f601fc8ed66e4108c5b8f12474e06520de57b8fdcc484bc
test = Scrypt(itterations=16384, memory_cost=8, parallel_cost=2, keylength=64)
out = test.hash(password = b"correct horse battery staple", salt = b"seasalt")
print(out.hex())
#e3e97ec22c635ca626a6e977ae90c69845ee4c716b57e9c00757e508822fedd83d1d0539d2de1c241b830d4ce59d0bcba72d482217f193af07a125eb1c67455f