Golang Age with ScryptScrypt supports the generation of a symmetric key using a passwords. Overall Age integrates with this a SetWorkFactor() value. The higher this value, the more work will be involved in generating the symmetric key. Scrypt is a password-based key derivation function which produces a hash with a salt and iterations. The iteration count slows down the cracking and the salt makes pre-computation difficult. The main parameters are: passphrase (P); salt (S); Blocksize (r) and CPU/Memory cost parameter (N - a power of 2). The SetWorkFactor() method sets the LogN parameter - this is recoemmended to be 14. |
Outline
The focus of password-based key derivation functions (KDFs) is to derive a secret from a secret. This has included the usage of crypt, PBKDF2 and bcrypt, and which use a number of cryptography rounds to slow down the operation and general add cost. Unfortunately, as the processor gets faster, the barrier caused by the number of iterations will reduce. This is especially the case if we use GPUs. Overall, PBKDF2 does not use up much memory, and so does not make things difficult for the GPU.
Scrypt is based on an original paper from Colin Percival [2]:
The paper made the following estimates for costings:
with this we see that scrypt has a significantly higher cost for cracking than bcrypt and PBKDF2. In this case, for the approximately the same time to generate a key for each method, the cost for scrypt is always much higher. For 10 characters, we see that PBKDF2 (5.0 s) has a cost of \$10m, while the same equivalent with scrypt is \$210 billion. Even with six characters, scrypt has a cost of \$900 to crack.
It uses a passphrase and a salt value, and computes a memory-hard compution. For this it different approach to most KDFs with a number of parameters: Blocksize (r) and CPU/Memory cost parameter (N — a power of 2) and the amount of parallelisation (p). At the core of the computation is not a hash function but Salsa20/8 Core — and which is a round-reduced variant of the Salsa20 Core [1].
Overall, it is possible to run the system with N, r, and p, and that relates to the amout of memory and computing power available, and the amount of parallelization required. It is seen that r = 8 and p = 1 gives good results.
An evaluation in 2016, outlined the effect that the values of \(2^N\), \(r\) and \(p\) have on the time to compute the key:
In this we see that increasing the parameters increases the size of the required memory. If a GPU has a certain size of memory, we can make sure that we overflow it. Recommended parameters are:
N: 16384 (2^14) r: 8 p: 1
Coding
The Python code for this is:
package main import ( "bytes" "encoding/base64" "fmt" "io" "log" "os" "filippo.io/age" ) func main() { msg := "Hello" password := "Qwerty123" argCount := len(os.Args[1:]) if argCount > 0 { msg = os.Args[1] } if argCount > 1 { password = os.Args[2] } r, _ := age.NewScryptRecipient(password) r.SetWorkFactor(3) out := &bytes.Buffer{} w, _ := age.Encrypt(out, r) if _, err := io.WriteString(w, msg); err != nil { log.Fatalf("Failed to write to encrypted file: %v", err) } fmt.Printf("Message:\t%v\n", msg) fmt.Printf("Passowrd:\t%v\n", password) fmt.Printf("\nEncrypted file size: %d\nCipher\t%v\n", out.Len(), base64.StdEncoding.EncodeToString(out.Bytes())) w.Close() i, _ := age.NewScryptIdentity(password) dec, _ := age.Decrypt(out, i) buf := new(bytes.Buffer) buf.ReadFrom(dec) str := buf.String() fmt.Println("\n\nDecrypted string\t ", str) }
and a sample run is:
Message: hello Passowrd: Qwerty123 Encrypted file size: 166 Cipher YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBsVTQxVzBQT2E2NXBZVXpFcllZMGpnIDE1CkpGYi93Wm1IeGJsQWRSdVplMFoyYWhTQ0NoUFF0bHA1YnFZbVZjZlR6aDgKLS0tIFJWUDZ5WXhNK3FjRTVKQXdVa3lMSXp3bTl0ZXlLZTJCSlllYkpsR0hFQ1UK7vVJcwRDJ4AF/VI8o09YSA== Decrypted string hello