scrypt.go (3750B)
1 // Copyright 2019 Google LLC 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd 6 7 package age 8 9 import ( 10 "crypto/rand" 11 "errors" 12 "fmt" 13 "strconv" 14 15 "github.com/FiloSottile/age/internal/format" 16 "golang.org/x/crypto/chacha20poly1305" 17 "golang.org/x/crypto/scrypt" 18 ) 19 20 type ScryptRecipient struct { 21 password []byte 22 workFactor int 23 } 24 25 var _ Recipient = &ScryptRecipient{} 26 27 func (*ScryptRecipient) Type() string { return "scrypt" } 28 29 func NewScryptRecipient(password string) (*ScryptRecipient, error) { 30 if len(password) == 0 { 31 return nil, errors.New("empty scrypt password") 32 } 33 r := &ScryptRecipient{ 34 password: []byte(password), 35 // TODO: automatically scale this to 1s (with a min) in the CLI. 36 workFactor: 18, // 1s on a modern machine 37 } 38 return r, nil 39 } 40 41 // SetWorkFactor sets the scrypt work factor to 2^logN. 42 // It must be called before Wrap. 43 func (r *ScryptRecipient) SetWorkFactor(logN int) { 44 if logN > 30 || logN < 1 { 45 panic("age: SetWorkFactor called with illegal value") 46 } 47 r.workFactor = logN 48 } 49 50 func (r *ScryptRecipient) Wrap(fileKey []byte) (*format.Recipient, error) { 51 salt := make([]byte, 16) 52 if _, err := rand.Read(salt[:]); err != nil { 53 return nil, err 54 } 55 56 logN := r.workFactor 57 l := &format.Recipient{ 58 Type: "scrypt", 59 Args: []string{format.EncodeToString(salt), strconv.Itoa(logN)}, 60 } 61 62 k, err := scrypt.Key(r.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize) 63 if err != nil { 64 return nil, fmt.Errorf("failed to generate scrypt hash: %v", err) 65 } 66 67 wrappedKey, err := aeadEncrypt(k, fileKey) 68 if err != nil { 69 return nil, err 70 } 71 l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") 72 73 return l, nil 74 } 75 76 type ScryptIdentity struct { 77 password []byte 78 maxWorkFactor int 79 } 80 81 var _ Identity = &ScryptIdentity{} 82 83 func (*ScryptIdentity) Type() string { return "scrypt" } 84 85 func NewScryptIdentity(password string) (*ScryptIdentity, error) { 86 if len(password) == 0 { 87 return nil, errors.New("empty scrypt password") 88 } 89 i := &ScryptIdentity{ 90 password: []byte(password), 91 maxWorkFactor: 22, // 15s on a modern machine 92 } 93 return i, nil 94 } 95 96 // SetWorkFactor sets the maximum accepted scrypt work factor to 2^logN. 97 // It must be called before Unwrap. 98 func (i *ScryptIdentity) SetMaxWorkFactor(logN int) { 99 if logN > 30 || logN < 1 { 100 panic("age: SetMaxWorkFactor called with illegal value") 101 } 102 i.maxWorkFactor = logN 103 } 104 105 func (i *ScryptIdentity) Unwrap(block *format.Recipient) ([]byte, error) { 106 if block.Type != "scrypt" { 107 return nil, errors.New("wrong recipient block type") 108 } 109 if len(block.Args) != 2 { 110 return nil, errors.New("invalid scrypt recipient block") 111 } 112 salt, err := format.DecodeString(block.Args[0]) 113 if err != nil { 114 return nil, fmt.Errorf("failed to parse scrypt salt: %v", err) 115 } 116 if len(salt) != 16 { 117 return nil, errors.New("invalid scrypt recipient block") 118 } 119 logN, err := strconv.Atoi(block.Args[1]) 120 if err != nil { 121 return nil, fmt.Errorf("failed to parse scrypt work factor: %v", err) 122 } 123 if logN > i.maxWorkFactor { 124 return nil, fmt.Errorf("scrypt work factor too large: %v", logN) 125 } 126 if logN <= 0 { 127 return nil, fmt.Errorf("invalid scrypt work factor: %v", logN) 128 } 129 wrappedKey, err := format.DecodeString(string(block.Body)) 130 if err != nil { 131 return nil, fmt.Errorf("failed to parse scrypt recipient: %v", err) 132 } 133 134 k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize) 135 if err != nil { 136 return nil, fmt.Errorf("failed to generate scrypt hash: %v", err) 137 } 138 139 fileKey, err := aeadDecrypt(k, wrappedKey) 140 if err != nil { 141 return nil, fmt.Errorf("failed to decrypt file key: %v", err) 142 } 143 return fileKey, nil 144 }