Lyra2

Lyra2 is a Key Derivation Function (KDF), also called Password Hashing Schemes (PHS), that received a special recognition during the Password Hashing Competition in July 2015.[1] It was designed by Marcos A. Simplicio Jr, Leonardo C. Almeida, Ewerton R. Andrade, Paulo C. F. dos Santos, and Paulo S. L. M. Barreto from Escola Politécnica da Universidade de São Paulo.[2]

Lyra2 is a improvement of Lyra,[3][4] previously proposed by the same authors. Lyra2 preserves the security, efficiency and flexibility of its predecessor, including: (1) the ability to configure the desired amount of memory, processing time and parallelism to be used by the algorithm; and (2) the capacity of providing a high memory usage with a processing time similar to that obtained with scrypt. In addition, it brings important improvements when compared to its predecessor:

Lyra2 is released under a public domain, and provides two main extensions:

This algorithm enables parameterization in terms of:

Strengths

Design

As any PHS, Lyra2 takes as input a salt and a password, creating a pseudorandom output that can then be used as key material for cryptographic algorithms or as an authentication string.[5]

Internally, the scheme's memory is organized as a matrix that is expected to remain in memory during the whole password hashing process: since its cells are iteratively read and written, discarding a cell for saving memory leads to the need of recomputing it whenever it is accessed once again, until the point it was last modified.[6]

The construction and visitation of the matrix is done using a stateful combination of the absorbing, squeezing and duplexing operations of the underlying sponge (i.e., its internal state is never reset to zero), ensuring the sequential nature of the whole process.

Also, the number of times the matrix's cells are revisited after initialization is defined by the user, allowing Lyra2's execution time to be fine-tuned according to the target platform's resources.

# *** Functions/symbols ***
# ||				            Concatenate two strings
# ^				                Bitwise XOR
# [+]				            Wordwise add operation (i.e., ignoring carries between words)
# %				                Modulus
# W				                The target machine's word size (usually, 32 or 64)
# omega				            Number of bits to be used in rotations (recommended: a multiple of the machine’s word size, W)
# >>>				            Right rotation
# rho				            Number of rounds for reduced squeeze or duplexing operations
# blen				            Sponge's block size in bytes 
# H or H_i			            Sponge with block size blen (in bytes) and underlying permutation f
# H.absorb(input)		        Sponge's absorb operation on input
# H.squeeze(len)		        Sponge's squeeze operation of len bytes
# H.squeeze_{rho}(len)		    Sponge's squeeze operation of len bytes using rho rounds of f
# H.duplexing(input,len)	    Sponge's duplexing operation on input, producing len bytes
# H.duplexing_{rho}(input,len)  Sponge's duplexing operation on input, using rho rounds of f, producing len bytes
# pad(string)			        Pads a string to a multiple of blen bytes (padding rule: 10*1)
# lsw(input)			        The least significant word of input
# len(string)			        Length of a string, in bytes
# syncThreads()			        Synchronize parallel threads
# swap(input1,input2)		    Swap the value of two inputs
# C				                Number of columns on the memory matrix (usually, 64, 128, 256, 512 or 1024)
# P				                Degree of parallelism (P >= 1 and (m_cost/2) % P = 0)

# *** Inputs ***
# password		
# salt			
# t_cost		
# m_cost		
# outlen		

# *** Algorithm without parallelism ***

# ** Bootstrapping phase: Initializes the sponge's state and local variables

# Byte representation of input parameters (others can be added)
params =  outlen || len(password) || len(salt) || t_cost || m_cost || C

# Initializes the sponge's state (after that, password can be overwritten)
H.absorb( pad(password || salt || params) )

# Initializes visitation step, window and first rows that will feed 
gap = 1
stp = 1
wnd = 2
sqrt = 2

prev0 = 2
row1 = 1
prev1 = 0

# ** Setup phase: Initializes a (m_cost x C) memory matrix, it's cells having blen-byte cells

# Initializes M[0], M[1] and M[2]
for col = 0 to C-1
	M[0][C-1-col] = H.squeeze_{rho}(blen)
for col = 0 to C-1
	M[1][C-1-col] = H.duplexing_{rho}( M[0][col], blen)
for col = 0 to C-1
	M[2][C-1-col] = H.duplexing_{rho}( M[1][col], blen)

# Filling Loop: initializes remainder rows
for row0 = 3 to m_cost-1
	# Columns Loop: M[row0] is initialized and M[row1] is updated
	for col = 0 to C-1
		rand = H.duplexing_{rho}( M[row1][col] [+] M[prev0][col] [+] M[prev1][col], blen)
		M[row0][C-1-col] = M[prev0][col] ^ rand
		M[row1][col] = M[row1][col] ^ ( rand >>> omega )

	# Rows to be revisited in next loop
	prev0 = row0
	prev1 = row1
	row1 = (row1 + stp) % wnd

	# Window fully revisited
	if (row1 = 0)
		# Doubles window and adjusts step
		wnd = 2 * wnd
		stp = sqrt + gap
		gap = -gap
		
		# Doubles sqrt every other iteration
		if (gap = -1)
			sqrt = 2 * sqrt
	
# ** Wandering phase: Iteratively overwrites pseudorandom cells of the memory matrix

# Visitation Loop: (2 * m_cost * t_cost) rows revisited in pseudorandom fashion
for wCount = 0 to ( (m_cost * t_cost) - 1)
	# Picks pseudorandom rows
	row0 = lsw(rand) % m_cost
	row1 = lsw( rand >>> omega ) % m_cost

	# Columns Loop: updates both M[row0] and M[row1]
	for col = 0 to C-1
		# Picks pseudorandom columns
		col0 = lsw( ( rand >>> omega ) >>> omega ) % C
		col1 = lsw( ( ( rand >>> omega ) >>> omega ) >>> omega ) % C

		rand = H.duplexing_{rho}( M[row0][col] [+] M[row1][col] [+] M[prev0][col0] [+] M[prev1][col1], blen)
		M[row0][col] = M[row0][col] ^ rand
		M[row1][col] = M[row1][col] ^ ( rand >>> omega )

	# Next iteration revisits most recently updated rows
	prev0 = row0
	prev1 = row1

# ** Wrap-up phase: output computation

# Absorbs a final column with a full-round sponge
H.absorb( M[row0][0] )

# Squeezes outlen bits with a full-round sponge
output = H.squeeze(outlen)

# Provides outlen-long bitstring as output
return output

# *** Algorithm with parallelism ***

for each i in [0,P[
	# ** Bootstrapping phase: Initializes the sponge's state and local variables
	
	# Byte representation of input parameters (others can be added)
	params =  outlen || len(password) || len(salt) || t_cost || m_cost || C || P || i

	# Initializes the sponge's state (after that, password can be overwritten)
	H_i.absorb( pad(password || salt || params) )

	# Initializes visitation step, window and first rows that will feed 
	gap = 1
	stp = 1
	wnd = 2
	sqrt = 2
	sync = 4
	j = i

	prev0 = 2
	rowP = 1
	prevP = 0

	# ** Setup phase: Initializes a (m_cost x C) memory matrix, it's cells having blen-byte cells

	# Initializes M_i[0], M_i[1] and M_i[2]
	for col = 0 to C-1
		M_i[0][C-1-col] = H_i.squeeze_{rho}(blen)
	for col = 0 to C-1
		M_i[1][C-1-col] = H_i.duplexing_{rho}( M_i[0][col], blen)
	for col = 0 to C-1
		M_i[2][C-1-col] = H_i.duplexing_{rho}( M_i[1][col], blen)

	# Filling Loop: initializes remainder rows
	for row0 = 3 to ( (m_cost / P) - 1 )
		# Columns Loop: M_i[row0] is initialized and M_j[row1] is updated
		for col = 0 to C-1
			rand = H_i.duplexing_{rho}( M_j[rowP][col] [+] M_i[prev0][col] [+] M_j[prevP][col], blen)
			M_i[row0][C-1-col] = M_i[prev0][col] ^ rand
			M_j[rowP][col] = M_j[rowP][col] ^ ( rand >>> omega )

		# Rows to be revisited in next loop
		prev0 = row0
		prevP = rowP
		rowP = (rowP + stp) % wnd

		# Window fully revisited
		if (rowP = 0)
			# Doubles window and adjusts step
			wnd = 2 * wnd
			stp = sqrt + gap
			gap = -gap
		
			# Doubles sqrt every other iteration
			if (gap = -1)
				sqrt = 2 * sqrt
		
		# Synchronize point
		if (row0 = sync)
			sync = sync + (sqrt / 2)
			j = (j + 1) % P
			syncThreads()

	syncThreads()
	
	# ** Wandering phase: Iteratively overwrites pseudorandom cells of the memory matrix

	wnd = m_cost / (2 * P)
	sync = sqrt
	off0 = 0
	offP = wnd

	# Visitation Loop: (2 * m_cost * t_cost / P) rows revisited in pseudorandom fashion
	for wCount = 0 to ( ( (m_cost * t_cost) / P) - 1)
		# Picks pseudorandom rows and slices (j)
		row0 = off0 + (lsw(rand) % wnd)
		rowP = offP + (lsw( rand >>> omega ) % wnd)
		j = lsw( ( rand >>> omega ) >>> omega ) % P

		# Columns Loop: update M_i[row0]
		for col = 0 to C-1
			# Picks pseudorandom column	
			col0 = lsw( ( ( rand >>> omega ) >>> omega ) >>> omega ) % C

			rand = H_i.duplexing_{rho}( M_i[row0][col] [+] M_i[prev0][col0] [+] M_j[rowP][col], blen)
			M_i[row0][col] = M_i[row0][col] ^ rand

		# Next iteration revisits most recently updated rows
		prev0 = row0
		
		# Synchronize point
		if (wCount = sync)
			sync = sync + sqrt
			swap(off0,offP)
			syncThreads()

	syncThreads()

	# ** Wrap-up phase: output computation

	# Absorbs a final column with a full-round sponge
	H_i.absorb( M_i[row0][0] )

	# Squeezes outlen bits with a full-round sponge
	output_i = H_i.squeeze(outlen)

# Provides outlen-long bitstring as output
return output_0 ^ ... ^ output_{P-1}

Security Analysis

Against Lyra2, the processing cost of attacks using 1/2^{n+2}  of the amount of memory employed by a legitimate user is expected to be between O(2^{2nT}R^{3}) and O(2^{2nT}R^{n+2}), the latter being a better estimate for n \gg 1, instead of the O(R) achieved when the amount of memory is O(R), where T is a user-defined parameter to define a processing time.

This compares well to scrypt, which displays a cost of O(R^{2}) when the memory usage is O(1),[7] and with other solutions in the literature, for which the best result is O(R^{T+1}).[3][8][9][10]

Nonetheless, in practice these solutions usually involve a value of R (memory usage) lower than those attained with the Lyra2 for the same processing time.[11][12][13][14][15]

Performance

Performance of SSE-enabled Lyra2, for C = 256, ρ = 1, p = 1, and different T and R settings, compared with SSE-enabled scrypt and memory-hard PHC finalists with minimum parameters.

The processing time obtained with a SSE single-core implementation of Lyra2 are illustrated in the figure besides.

This figure was extracted from,[6] and is very similar of third-party benchmarks performed during the PHC context.[11][12][13][14][15]

The results depicted correspond to the average execution time of Lyra2 configured with C=256, \rho=1, b=768 bits (i.e., the inner state has 256 bits), and different T and R settings, giving an overall idea of possible combinations of parameters and the corresponding usage of resources.

As shown in this figure, Lyra2 is able to execute in: less than 1 s while using up to 400 MB (with R = 2^{14} and T=5) or up to 1 GB of memory (with R \approx 4.2\cdot10^{4} and T=1); or in less than 5 s with 1.6 GB (with R = 2^{16} and T=6).

All tests were performed on an Intel Xeon E5-2430 (2.20 GHz with 12 Cores, 64 bits) equipped with 48 GB of DRAM, running Ubuntu 14.04 LTS 64 bits, and the source code was compiled using gcc 4.9.2.[6]

External links

References

  1. "Password Hashing Competition". password-hashing.net. Retrieved 2016-03-22.
  2. 1 2 "Cryptology ePrint Archive: Report 2015/136". eprint.iacr.org. Retrieved 2016-03-22.
  3. 1 2 Almeida, Leonardo C.; Andrade, Ewerton R.; Barreto, Paulo S. L. M.; Jr, Marcos A. Simplicio (2014-01-04). "Lyra: password-based key derivation with tunable memory and processing costs". Journal of Cryptographic Engineering 4 (2): 75–89. doi:10.1007/s13389-013-0063-5. ISSN 2190-8508.
  4. 1 2 "Cryptology ePrint Archive: Report 2014/030". eprint.iacr.org. Retrieved 2016-03-22.
  5. Chen, Lily (Recommendation for Key Derivation Using Pseudorandom Functions (Revised)). "NIST Special Publication 800-108" (PDF). Computer Security. NIST. Retrieved http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf. Check date values in: |access-date=, |date= (help)
  6. 1 2 3 Andrade, E.; Jr, M. Simplicio; Barreto, P.; Santos, P. (2016-01-01). "Lyra2: efficient password hashing with high security against time-memory trade-offs". IEEE Transactions on Computers PP (99): 1–1. doi:10.1109/TC.2016.2516011. ISSN 0018-9340.
  7. Percival, Colin (Academic paper). "Stronger Key Derivation via Sequential Memory-Hard Functions" (PDF). TARSNAP. The Technical BSD Conference. Retrieved https://www.tarsnap.com/scrypt/scrypt.pdf. Check date values in: |access-date=, |date= (help)
  8. "Cryptology ePrint Archive: Report 2013/525". eprint.iacr.org. Retrieved 2016-03-22.
  9. Schmidt, Sascha (Master’s Thesis). "Implementation of the Catena Password-Scrambling Framework" (PDF). Bauhaus-Universität Weimar. Faculty of Media. Retrieved https://www.uni-weimar.de/fileadmin/user/fak/medien/professuren/Mediensicherheit/Research/Theses/sascha-schmidt-master-thesis-catena.pdf. Check date values in: |access-date=, |date= (help)
  10. "P-H-C/phc-winner-argon2" (PDF). GitHub. Retrieved 2016-03-22.
  11. 1 2 "Gmane -- Another PHC candidates mechanical tests". article.gmane.org. Retrieved 2016-03-22.
  12. 1 2 "Gmane -- A review per day Lyra2". article.gmane.org. Retrieved 2016-03-22.
  13. 1 2 "Gmane -- Lyra2 initial review". article.gmane.org. Retrieved 2016-03-22.
  14. 1 2 "Gmane -- Memory performance and ASIC attacks". article.gmane.org. Retrieved 2016-03-22.
  15. 1 2 "Gmane -- Quick analysis of Argon". article.gmane.org. Retrieved 2016-03-22.
This article is issued from Wikipedia - version of the Thursday, March 24, 2016. The text is available under the Creative Commons Attribution/Share Alike but additional terms may apply for the media files.