github.com/decred/dcrlnd@v0.7.6/aezeed/cipherseed.go (about) 1 package aezeed 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/binary" 7 "hash/crc32" 8 "io" 9 "time" 10 11 "github.com/Yawning/aez" 12 "github.com/kkdai/bstream" 13 14 "golang.org/x/crypto/scrypt" 15 ) 16 17 const ( 18 // CipherSeedVersion is the current version of the aezeed scheme as 19 // defined in this package. This version indicates the following 20 // parameters for the deciphered cipher seed: a 1 byte version, 2 bytes 21 // for the Decred Days Genesis timestamp, and 16 bytes for entropy. It 22 // also governs how the cipher seed should be enciphered. In this 23 // version we take the deciphered seed, create a 5 byte salt, use that 24 // with an optional passphrase to generate a 32-byte key (via scrypt), 25 // then encipher with aez (using the salt and version as AD). The final 26 // enciphered seed is: version || ciphertext || salt. 27 CipherSeedVersion uint8 = 0 28 29 // DecipheredCipherSeedSize is the size of the plaintext seed resulting 30 // from deciphering the cipher seed. The size consists of the 31 // following: 32 // 33 // * 1 byte version || 2 bytes timestamp || 16 bytes of entropy. 34 // 35 // The version is used by wallets to know how to re-derive relevant 36 // addresses, the 2 byte timestamp a DDG (Decred Days Genesis) offset, 37 // and finally, the 16 bytes to be used to generate the HD wallet seed. 38 DecipheredCipherSeedSize = 19 39 40 // EncipheredCipherSeedSize is the size of the fully encoded+enciphered 41 // cipher seed. We first obtain the enciphered plaintext seed by 42 // carrying out the enciphering as governed in the current version. We 43 // then take that enciphered seed (now 19+4=23 bytes due to ciphertext 44 // expansion, essentially a checksum) and prepend a version, then 45 // append the salt, and then take a checksum of everything. The 46 // checksum allows us to verify that the user input the correct set of 47 // words, then we can verify the passphrase due to the internal MAC 48 // equiv. The final breakdown is: 49 // 50 // * 1 byte version || 23 byte enciphered seed || 5 byte salt || 4 byte checksum 51 // 52 // With CipherSeedVersion we encipher as follows: we use 53 // scrypt(n=32768, r=8, p=1) to derive a 32-byte key from an optional 54 // user passphrase. We then encipher the plaintext seed using a value 55 // of tau (with aez) of 8-bytes (so essentially a 32-bit MAC). When 56 // enciphering, we include the version and scrypt salt as the AD. This 57 // gives us a total of 33 bytes. These 33 bytes fit cleanly into 24 58 // mnemonic words. 59 EncipheredCipherSeedSize = 33 60 61 // CipherTextExpansion is the number of bytes that will be added as 62 // redundancy for the enciphering scheme implemented by aez. This can 63 // be seen as the size of the equivalent MAC. 64 CipherTextExpansion = 4 65 66 // EntropySize is the number of bytes of entropy we'll use the generate 67 // the seed. 68 EntropySize = 16 69 70 // NumMnemonicWords is the number of words that an encoded cipher seed 71 // will result in. 72 NumMnemonicWords = 24 73 74 // saltSize is the size of the salt we'll generate to use with scrypt 75 // to generate a key for use within aez from the user's passphrase. The 76 // role of the salt is to make the creation of rainbow tables 77 // infeasible. 78 saltSize = 5 79 80 // adSize is the size of the encoded associated data that will be 81 // passed into aez when enciphering and deciphering the seed. The AD 82 // itself (associated data) is just the CipherSeedVersion and salt. 83 adSize = 6 84 85 // checkSumSize is the size of the checksum applied to the final 86 // encoded ciphertext. 87 checkSumSize = 4 88 89 // keyLen is the size of the key that we'll use for encryption with 90 // aez. 91 keyLen = 32 92 93 // BitsPerWord is the number of bits each word in the wordlist encodes. 94 // We encode our mnemonic using 24 words, so 264 bits (33 bytes). 95 BitsPerWord = 11 96 97 // saltOffset is the index within an enciphered cipherseed that marks 98 // the start of the salt. 99 saltOffset = EncipheredCipherSeedSize - checkSumSize - saltSize 100 101 // checkSumSize is the index within an enciphered cipher seed that 102 // marks the start of the checksum. 103 checkSumOffset = EncipheredCipherSeedSize - checkSumSize 104 ) 105 106 var ( 107 // Below at the default scrypt parameters that are tied to 108 // CipherSeedVersion zero. 109 scryptN = 32768 110 scryptR = 8 111 scryptP = 1 112 113 // crcTable is a table that presents the polynomial we'll use for 114 // computing our checksum. 115 crcTable = crc32.MakeTable(crc32.Castagnoli) 116 117 // defaultPassphrase is the default passphrase that will be used for 118 // encryption in the case that the user chooses not to specify their 119 // own passphrase. 120 defaultPassphrase = []byte("aezeed") 121 ) 122 123 var ( 124 // DecredGenesisDate is the timestamp of Decred's genesis block. 125 // We'll use this value in order to create a compact birthday for the 126 // seed. The birthday will be interested as the number of days since 127 // the genesis date. We refer to this time period as ADE (after Decred 128 // era). 129 DecredGenesisDate = time.Unix(1454954400, 0) 130 ) 131 132 // CipherSeed is a fully decoded instance of the aezeed scheme. At a high 133 // level, the encoded cipherseed is the enciphering of: a version byte, a set 134 // of bytes for a timestamp, the entropy which will be used to directly 135 // construct the HD seed, and finally a checksum over the rest. This scheme was 136 // created as the widely used schemes in the space lack two critical traits: a 137 // version byte, and a birthday timestamp. The version allows us to modify the 138 // details of the scheme in the future, and the birthday gives wallets a limit 139 // of how far back in the chain they'll need to start scanning. We also add an 140 // external version to the enciphering plaintext seed. With this addition, 141 // seeds are able to be "upgraded" (to diff params, or entirely diff crypt), 142 // while maintaining the semantics of the plaintext seed. 143 // 144 // The core of the scheme is the usage of aez to carefully control the size of 145 // the final encrypted seed. With the current parameters, this scheme can be 146 // encoded using a 24 word mnemonic. We use 4 bytes of ciphertext expansion 147 // when enciphering the raw seed, giving us the equivalent of 40-bit MAC (as we 148 // check for a particular seed version). Using the external 4 byte checksum, 149 // we're able to ensure that the user input the correct set of words. Finally, 150 // the password in the scheme is optional. If not specified, "aezeed" will be 151 // used as the password. Otherwise, the addition of the password means that 152 // users can encrypt the raw "plaintext" seed under distinct passwords to 153 // produce unique mnemonic phrases. 154 type CipherSeed struct { 155 // InternalVersion is the version of the plaintext cipherseed. This is 156 // to be used by wallets to determine if the seed version is compatible 157 // with the derivation schemes they know. 158 InternalVersion uint8 159 160 // Birthday is the time that the seed was created. This is expressed as 161 // the number of days since the timestamp in the Decred genesis block. 162 // We use days as seconds gives us wasted granularity. The oldest seed 163 // that we can encode using this format is through the date 2188. 164 Birthday uint16 165 166 // Entropy is a set of bytes generated via a CSPRNG. This is the value 167 // that should be used to directly generate the HD root, as defined 168 // within BIP0032. 169 Entropy [EntropySize]byte 170 171 // salt is the salt that was used to generate the key from the user's 172 // specified passphrase. 173 salt [saltSize]byte 174 } 175 176 // New generates a new CipherSeed instance from an optional source of entropy. 177 // If the entropy isn't provided, then a set of random bytes will be used in 178 // place. The final argument should be the time at which the seed was created. 179 func New(internalVersion uint8, entropy *[EntropySize]byte, 180 now time.Time) (*CipherSeed, error) { 181 182 // TODO(roasbeef): pass randomness source? to make fully determinsitc? 183 184 // If a set of entropy wasn't provided, then we'll read a set of bytes 185 // from the CSPRNG of our operating platform. 186 var seed [EntropySize]byte 187 if entropy == nil { 188 if _, err := rand.Read(seed[:]); err != nil { 189 return nil, err 190 } 191 } else { 192 // Otherwise, we'll copy the set of bytes. 193 copy(seed[:], entropy[:]) 194 } 195 196 // To compute our "birthday", we'll first use the current time, then 197 // subtract that from the Decred Genesis Date. We'll then convert that 198 // value to days. 199 birthday := uint16(now.Sub(DecredGenesisDate) / (time.Hour * 24)) 200 201 c := &CipherSeed{ 202 InternalVersion: internalVersion, 203 Birthday: birthday, 204 Entropy: seed, 205 } 206 207 // Next, we'll read a random salt that will be used with scrypt to 208 // eventually derive our key. 209 if _, err := rand.Read(c.salt[:]); err != nil { 210 return nil, err 211 } 212 213 return c, nil 214 } 215 216 // encode attempts to encode the target cipherSeed into the passed io.Writer 217 // instance. 218 func (c *CipherSeed) encode(w io.Writer) error { 219 err := binary.Write(w, binary.BigEndian, c.InternalVersion) 220 if err != nil { 221 return err 222 } 223 224 if err := binary.Write(w, binary.BigEndian, c.Birthday); err != nil { 225 return err 226 } 227 228 if _, err := w.Write(c.Entropy[:]); err != nil { 229 return err 230 } 231 232 return nil 233 } 234 235 // decode attempts to decode an encoded cipher seed instance into the target 236 // CipherSeed struct. 237 func (c *CipherSeed) decode(r io.Reader) error { 238 err := binary.Read(r, binary.BigEndian, &c.InternalVersion) 239 if err != nil { 240 return err 241 } 242 243 if err := binary.Read(r, binary.BigEndian, &c.Birthday); err != nil { 244 return err 245 } 246 247 if _, err := io.ReadFull(r, c.Entropy[:]); err != nil { 248 return err 249 } 250 251 return nil 252 } 253 254 // encodeAD returns the fully encoded associated data for use when performing 255 // our current enciphering operation. The AD is: version || salt. 256 func encodeAD(version uint8, salt [saltSize]byte) [adSize]byte { 257 var ad [adSize]byte 258 ad[0] = version 259 copy(ad[1:], salt[:]) 260 261 return ad 262 } 263 264 // extractAD extracts an associated data from a fully encoded and enciphered 265 // cipher seed. This is to be used when attempting to decrypt an enciphered 266 // cipher seed. 267 func extractAD(encipheredSeed [EncipheredCipherSeedSize]byte) [adSize]byte { 268 var ad [adSize]byte 269 ad[0] = encipheredSeed[0] 270 271 copy(ad[1:], encipheredSeed[saltOffset:checkSumOffset]) 272 273 return ad 274 } 275 276 // encipher takes a fully populated cipherseed instance, and enciphers the 277 // encoded seed, then appends a randomly generated seed used to stretch the 278 // passphrase out into an appropriate key, then computes a checksum over the 279 // preceding. 280 func (c *CipherSeed) encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { 281 var cipherSeedBytes [EncipheredCipherSeedSize]byte 282 283 // If the passphrase wasn't provided, then we'll use the string 284 // "aezeed" in place. 285 passphrase := pass 286 if len(passphrase) == 0 { 287 passphrase = defaultPassphrase 288 } 289 290 // With our salt pre-generated, we'll now run the password through a 291 // KDF to obtain the key we'll use for encryption. 292 key, err := scrypt.Key( 293 passphrase, c.salt[:], scryptN, scryptR, scryptP, keyLen, 294 ) 295 if err != nil { 296 return cipherSeedBytes, err 297 } 298 299 // Next, we'll encode the serialized plaintext cipherseed into a buffer 300 // that we'll use for encryption. 301 var seedBytes bytes.Buffer 302 if err := c.encode(&seedBytes); err != nil { 303 return cipherSeedBytes, err 304 } 305 306 // With our plaintext seed encoded, we'll now construct the AD that 307 // will be passed to the encryption operation. This ensures to 308 // authenticate both the salt and the external version. 309 ad := encodeAD(CipherSeedVersion, c.salt) 310 311 // With all items assembled, we'll now encipher the plaintext seed 312 // with our AD, key, and MAC size. 313 cipherSeed := seedBytes.Bytes() 314 cipherText := aez.Encrypt( 315 key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, 316 ) 317 318 // Finally, we'll pack the {version || ciphertext || salt || checksum} 319 // seed into a byte slice for encoding as a mnemonic. 320 cipherSeedBytes[0] = CipherSeedVersion 321 copy(cipherSeedBytes[1:saltOffset], cipherText) 322 copy(cipherSeedBytes[saltOffset:], c.salt[:]) 323 324 // With the seed mostly assembled, we'll now compute a checksum all the 325 // contents. 326 checkSum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable) 327 328 // With our checksum computed, we can finish encoding the full cipher 329 // seed. 330 var checkSumBytes [4]byte 331 binary.BigEndian.PutUint32(checkSumBytes[:], checkSum) 332 copy(cipherSeedBytes[checkSumOffset:], checkSumBytes[:]) 333 334 return cipherSeedBytes, nil 335 } 336 337 // cipherTextToMnemonic converts the aez ciphertext appended with the salt to a 338 // 24-word mnemonic pass phrase. 339 func cipherTextToMnemonic(cipherText [EncipheredCipherSeedSize]byte) (Mnemonic, error) { 340 var words [NumMnemonicWords]string 341 342 // First, we'll convert the ciphertext itself into a bitstream for easy 343 // manipulation. 344 cipherBits := bstream.NewBStreamReader(cipherText[:]) 345 346 // With our bitstream obtained, we'll read 11 bits at a time, then use 347 // that to index into our word list to obtain the next word. 348 for i := 0; i < NumMnemonicWords; i++ { 349 index, err := cipherBits.ReadBits(BitsPerWord) 350 if err != nil { 351 return Mnemonic{}, err 352 } 353 354 words[i] = DefaultWordList[index] 355 } 356 357 return words, nil 358 } 359 360 // ToMnemonic maps the final enciphered cipher seed to a human readable 24-word 361 // mnemonic phrase. The password is optional, as if it isn't specified aezeed 362 // will be used in its place. 363 func (c *CipherSeed) ToMnemonic(pass []byte) (Mnemonic, error) { 364 // First, we'll convert the valid seed triple into an aez cipher text 365 // with our KDF salt appended to it. 366 cipherText, err := c.encipher(pass) 367 if err != nil { 368 return Mnemonic{}, err 369 } 370 371 // Now that we have our cipher text, we'll convert it into a mnemonic 372 // phrase. 373 return cipherTextToMnemonic(cipherText) 374 } 375 376 // Encipher maps the cipher seed to an aez ciphertext using an optional 377 // passphrase. 378 func (c *CipherSeed) Encipher(pass []byte) ([EncipheredCipherSeedSize]byte, error) { 379 return c.encipher(pass) 380 } 381 382 // BirthdayTime returns the cipher seed's internal birthday format as a native 383 // golang Time struct. 384 func (c *CipherSeed) BirthdayTime() time.Time { 385 offset := time.Duration(c.Birthday) * 24 * time.Hour 386 return DecredGenesisDate.Add(offset) 387 } 388 389 // Mnemonic is a 24-word passphrase as of CipherSeedVersion zero. This 390 // passphrase encodes an encrypted seed triple (version, birthday, entropy). 391 // Additionally, we also encode the salt used with scrypt to derive the key 392 // that the cipher text is encrypted with, and the version which tells us how 393 // to decipher the seed. 394 type Mnemonic [NumMnemonicWords]string 395 396 // mnemonicToCipherText converts a 24-word mnemonic phrase into a 33 byte 397 // cipher text. 398 // 399 // NOTE: This assumes that all words have already been checked to be amongst 400 // our word list. 401 func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte { 402 var cipherText [EncipheredCipherSeedSize]byte 403 404 // We'll now perform the reverse mapping to that of 405 // cipherTextToMnemonic: we'll get the index of the word, then write 406 // out that index to the bit stream. 407 cipherBits := bstream.NewBStreamWriter(EncipheredCipherSeedSize) 408 for _, word := range mnemonic { 409 // Using the reverse word map, we'll locate the index of this 410 // word within the word list. 411 index := uint64(ReverseWordMap[word]) 412 413 // With the index located, we'll now write this out to the 414 // bitstream, appending to what's already there. 415 cipherBits.WriteBits(index, BitsPerWord) 416 } 417 418 copy(cipherText[:], cipherBits.Bytes()) 419 420 return cipherText 421 } 422 423 // ToCipherSeed attempts to map the mnemonic to the original cipher text byte 424 // slice. Then we'll attempt to decrypt the ciphertext using aez with the 425 // passed passphrase, using the last 5 bytes of the ciphertext as a salt for 426 // the KDF. 427 func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) { 428 // First, we'll attempt to decipher the mnemonic by mapping back into 429 // our byte slice and applying our deciphering scheme. 430 plainSeed, err := m.Decipher(pass) 431 if err != nil { 432 return nil, err 433 } 434 435 // If decryption was successful, then we'll decode into a fresh 436 // CipherSeed struct. 437 var c CipherSeed 438 if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil { 439 return nil, err 440 } 441 442 return &c, nil 443 } 444 445 // decipherCipherSeed attempts to decipher the passed cipher seed ciphertext 446 // using the passed passphrase. This function is the opposite of 447 // the encipher method. 448 func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte, 449 pass []byte) ([DecipheredCipherSeedSize]byte, error) { 450 451 var plainSeed [DecipheredCipherSeedSize]byte 452 453 // Before we do anything, we'll ensure that the version is one that we 454 // understand. Otherwise, we won't be able to decrypt, or even parse 455 // the cipher seed. 456 if cipherSeedBytes[0] != CipherSeedVersion { 457 return plainSeed, ErrIncorrectVersion 458 } 459 460 // Next, we'll slice off the salt from the pass cipher seed, then 461 // snip off the end of the cipher seed, ignoring the version, and 462 // finally the checksum. 463 salt := cipherSeedBytes[saltOffset : saltOffset+saltSize] 464 cipherSeed := cipherSeedBytes[1:saltOffset] 465 checksum := cipherSeedBytes[checkSumOffset:] 466 467 // Before we perform any crypto operations, we'll re-create and verify 468 // the checksum to ensure that the user input the proper set of words. 469 freshChecksum := crc32.Checksum(cipherSeedBytes[:checkSumOffset], crcTable) 470 if freshChecksum != binary.BigEndian.Uint32(checksum) { 471 return plainSeed, ErrIncorrectMnemonic 472 } 473 474 // With the salt separated from the cipher text, we'll now obtain the 475 // key used for encryption. 476 key, err := scrypt.Key(pass, salt, scryptN, scryptR, scryptP, keyLen) 477 if err != nil { 478 return plainSeed, err 479 } 480 481 // We'll also extract the AD that will be required to properly pass the 482 // MAC check. 483 ad := extractAD(cipherSeedBytes) 484 485 // With the key, we'll attempt to decrypt the plaintext. If the 486 // ciphertext was altered, or the passphrase is incorrect, then we'll 487 // error out. 488 plainSeedBytes, ok := aez.Decrypt( 489 key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, 490 ) 491 if !ok { 492 return plainSeed, ErrInvalidPass 493 } 494 copy(plainSeed[:], plainSeedBytes) 495 496 return plainSeed, nil 497 498 } 499 500 // Decipher attempts to decipher the encoded mnemonic by first mapping to the 501 // original chipertext, then applying our deciphering scheme. ErrInvalidPass 502 // will be returned if the passphrase is incorrect. 503 func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, error) { 504 505 // Before we attempt to map the mnemonic back to the original 506 // ciphertext, we'll ensure that all the word are actually a part of 507 // the current default word list. 508 wordDict := make(map[string]struct{}, len(DefaultWordList)) 509 for _, word := range DefaultWordList { 510 wordDict[word] = struct{}{} 511 } 512 513 for i, word := range m { 514 if _, ok := wordDict[word]; !ok { 515 emptySeed := [DecipheredCipherSeedSize]byte{} 516 return emptySeed, ErrUnknownMnenomicWord{ 517 Word: word, 518 Index: uint8(i), 519 } 520 } 521 } 522 523 // If the passphrase wasn't provided, then we'll use the string 524 // "aezeed" in place. 525 passphrase := pass 526 if len(passphrase) == 0 { 527 passphrase = defaultPassphrase 528 } 529 530 // Next, we'll map the mnemonic phrase back into the original cipher 531 // text. 532 cipherText := mnemonicToCipherText(m) 533 534 // Finally, we'll attempt to decipher the enciphered seed. The result 535 // will be the raw seed minus the ciphertext expansion, external 536 // version, and salt. 537 return decipherCipherSeed(cipherText, passphrase) 538 } 539 540 // ChangePass takes an existing mnemonic, and passphrase for said mnemonic and 541 // re-enciphers the plaintext cipher seed into a brand new mnemonic. This can 542 // be used to allow users to re-encrypt the same seed with multiple pass 543 // phrases, or just change the passphrase on an existing seed. 544 func (m *Mnemonic) ChangePass(oldPass, newPass []byte) (Mnemonic, error) { 545 var newmnemonic Mnemonic 546 547 // First, we'll try to decrypt the current mnemonic using the existing 548 // passphrase. If this fails, then we can't proceed any further. 549 cipherSeed, err := m.ToCipherSeed(oldPass) 550 if err != nil { 551 return newmnemonic, err 552 } 553 554 // If the deciperhing was successful, then we'll now re-encipher using 555 // the new user provided passphrase. 556 return cipherSeed.ToMnemonic(newPass) 557 }