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  }