aezeed: re-encode salt correctly

This commit is contained in:
Oliver Gugger committed 2022-05-12 12:49:14 +02:00
1 parent f67776375f
commit 63e28a27b8
2 files changed
+36 -26

No files matched your search

+27 -21
View File
@@ -70,11 +70,11 @@ const (
// will result in. // will result in.
NumMnemonicWords = 24 NumMnemonicWords = 24
// saltSize is the size of the salt we'll generate to use with scrypt // SaltSize is the size of the salt we'll generate to use with scrypt
// to generate a key for use within aez from the user's passphrase. The // to generate a key for use within aez from the user's passphrase. The
// role of the salt is to make the creation of rainbow tables // role of the salt is to make the creation of rainbow tables
// infeasible. // infeasible.
saltSize = 5 SaltSize = 5
// adSize is the size of the encoded associated data that will be // adSize is the size of the encoded associated data that will be
// passed into aez when enciphering and deciphering the seed. The AD // passed into aez when enciphering and deciphering the seed. The AD
@@ -95,7 +95,7 @@ const (
// saltOffset is the index within an enciphered cipher seed that marks // saltOffset is the index within an enciphered cipher seed that marks
// the start of the salt. // the start of the salt.
saltOffset = EncipheredCipherSeedSize - checkSumSize - saltSize saltOffset = EncipheredCipherSeedSize - checkSumSize - SaltSize
// checkSumSize is the index within an enciphered cipher seed that // checkSumSize is the index within an enciphered cipher seed that
// marks the start of the checksum. // marks the start of the checksum.
@@ -169,7 +169,7 @@ type CipherSeed struct {
// salt is the salt that was used to generate the key from the user's // salt is the salt that was used to generate the key from the user's
// specified passphrase. // specified passphrase.
salt [saltSize]byte salt [SaltSize]byte
} }
// New generates a new CipherSeed instance from an optional source of entropy. // New generates a new CipherSeed instance from an optional source of entropy.
@@ -252,7 +252,7 @@ func (c *CipherSeed) decode(r io.Reader) error {
// encodeAD returns the fully encoded associated data for use when performing // encodeAD returns the fully encoded associated data for use when performing
// our current enciphering operation. The AD is: version || salt. // our current enciphering operation. The AD is: version || salt.
func encodeAD(version uint8, salt [saltSize]byte) [adSize]byte { func encodeAD(version uint8, salt [SaltSize]byte) [adSize]byte {
var ad [adSize]byte var ad [adSize]byte
ad[0] = version ad[0] = version
copy(ad[1:], salt[:]) copy(ad[1:], salt[:])
@@ -432,14 +432,16 @@ func mnemonicToCipherText(mnemonic *Mnemonic) [EncipheredCipherSeedSize]byte {
func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) { func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
// First, we'll attempt to decipher the mnemonic by mapping back into // First, we'll attempt to decipher the mnemonic by mapping back into
// our byte slice and applying our deciphering scheme. // our byte slice and applying our deciphering scheme.
plainSeed, err := m.Decipher(pass) plainSeed, salt, err := m.Decipher(pass)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If decryption was successful, then we'll decode into a fresh // If decryption was successful, then we'll decode into a fresh
// CipherSeed struct. // CipherSeed struct.
var c CipherSeed c := CipherSeed{
salt: salt,
}
if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil { if err := c.decode(bytes.NewReader(plainSeed[:])); err != nil {
return nil, err return nil, err
} }
@@ -451,21 +453,24 @@ func (m *Mnemonic) ToCipherSeed(pass []byte) (*CipherSeed, error) {
// using the passed passphrase. This function is the opposite of // using the passed passphrase. This function is the opposite of
// the encipher method. // the encipher method.
func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte, func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
pass []byte) ([DecipheredCipherSeedSize]byte, error) { pass []byte) ([DecipheredCipherSeedSize]byte, [SaltSize]byte, error) {
var plainSeed [DecipheredCipherSeedSize]byte var (
plainSeed [DecipheredCipherSeedSize]byte
salt [SaltSize]byte
)
// Before we do anything, we'll ensure that the version is one that we // Before we do anything, we'll ensure that the version is one that we
// understand. Otherwise, we won't be able to decrypt, or even parse // understand. Otherwise, we won't be able to decrypt, or even parse
// the cipher seed. // the cipher seed.
if cipherSeedBytes[0] != CipherSeedVersion { if cipherSeedBytes[0] != CipherSeedVersion {
return plainSeed, ErrIncorrectVersion return plainSeed, salt, ErrIncorrectVersion
} }
// Next, we'll slice off the salt from the pass cipher seed, then // Next, we'll slice off the salt from the pass cipher seed, then
// snip off the end of the cipher seed, ignoring the version, and // snip off the end of the cipher seed, ignoring the version, and
// finally the checksum. // finally the checksum.
salt := cipherSeedBytes[saltOffset : saltOffset+saltSize] copy(salt[:], cipherSeedBytes[saltOffset:saltOffset+SaltSize])
cipherSeed := cipherSeedBytes[1:saltOffset] cipherSeed := cipherSeedBytes[1:saltOffset]
checksum := cipherSeedBytes[checkSumOffset:] checksum := cipherSeedBytes[checkSumOffset:]
@@ -475,14 +480,14 @@ func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
cipherSeedBytes[:checkSumOffset], crcTable, cipherSeedBytes[:checkSumOffset], crcTable,
) )
if freshChecksum != binary.BigEndian.Uint32(checksum) { if freshChecksum != binary.BigEndian.Uint32(checksum) {
return plainSeed, ErrIncorrectMnemonic return plainSeed, salt, ErrIncorrectMnemonic
} }
// With the salt separated from the cipher text, we'll now obtain the // With the salt separated from the cipher text, we'll now obtain the
// key used for encryption. // key used for encryption.
key, err := scrypt.Key(pass, salt, scryptN, scryptR, scryptP, keyLen) key, err := scrypt.Key(pass, salt[:], scryptN, scryptR, scryptP, keyLen)
if err != nil { if err != nil {
return plainSeed, err return plainSeed, salt, err
} }
// We'll also extract the AD that will be required to properly pass the // We'll also extract the AD that will be required to properly pass the
@@ -496,11 +501,11 @@ func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil, key, nil, [][]byte{ad[:]}, CipherTextExpansion, cipherSeed, nil,
) )
if !ok { if !ok {
return plainSeed, ErrInvalidPass return plainSeed, salt, ErrInvalidPass
} }
copy(plainSeed[:], plainSeedBytes) copy(plainSeed[:], plainSeedBytes)
return plainSeed, nil return plainSeed, salt, nil
} }
@@ -508,7 +513,7 @@ func decipherCipherSeed(cipherSeedBytes [EncipheredCipherSeedSize]byte,
// original ciphertext, then applying our deciphering scheme. ErrInvalidPass // original ciphertext, then applying our deciphering scheme. ErrInvalidPass
// will be returned if the passphrase is incorrect. // will be returned if the passphrase is incorrect.
func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte, func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
error) { [SaltSize]byte, error) {
// Before we attempt to map the mnemonic back to the original // Before we attempt to map the mnemonic back to the original
// ciphertext, we'll ensure that all the word are actually a part of // ciphertext, we'll ensure that all the word are actually a part of
@@ -521,10 +526,11 @@ func (m *Mnemonic) Decipher(pass []byte) ([DecipheredCipherSeedSize]byte,
for i, word := range m { for i, word := range m {
if _, ok := wordDict[word]; !ok { if _, ok := wordDict[word]; !ok {
emptySeed := [DecipheredCipherSeedSize]byte{} emptySeed := [DecipheredCipherSeedSize]byte{}
return emptySeed, ErrUnknownMnemonicWord{ return emptySeed, [SaltSize]byte{},
Word: word, ErrUnknownMnemonicWord{
Index: uint8(i), Word: word,
} Index: uint8(i),
}
} }
} }
+9 -5
View File
@@ -16,7 +16,7 @@ type TestVector struct {
version uint8 version uint8
time time.Time time time.Time
entropy [EntropySize]byte entropy [EntropySize]byte
salt [saltSize]byte salt [SaltSize]byte
password []byte password []byte
expectedMnemonic [NumMnemonicWords]string expectedMnemonic [NumMnemonicWords]string
expectedBirthday uint16 expectedBirthday uint16
@@ -29,7 +29,7 @@ var (
0x0d, 0xe7, 0x95, 0xe4, 0x0d, 0xe7, 0x95, 0xe4,
0x1e, 0x0b, 0x4c, 0xfd, 0x1e, 0x0b, 0x4c, 0xfd,
} }
testSalt = [saltSize]byte{ testSalt = [SaltSize]byte{
0x73, 0x61, 0x6c, 0x74, 0x31, // equal to "salt1" 0x73, 0x61, 0x6c, 0x74, 0x31, // equal to "salt1"
} }
version0TestVectors = []TestVector{{ version0TestVectors = []TestVector{{
@@ -70,6 +70,7 @@ func assertCipherSeedEqual(t *testing.T, cipherSeed *CipherSeed,
) )
require.Equal(t, cipherSeed.Birthday, cipherSeed2.Birthday, "birthday") require.Equal(t, cipherSeed.Birthday, cipherSeed2.Birthday, "birthday")
require.Equal(t, cipherSeed.Entropy, cipherSeed2.Entropy, "entropy") require.Equal(t, cipherSeed.Entropy, cipherSeed2.Entropy, "entropy")
require.Equal(t, cipherSeed.salt, cipherSeed2.salt, "salt")
} }
// TestAezeedVersion0TestVectors tests some fixed test vector values against // TestAezeedVersion0TestVectors tests some fixed test vector values against
@@ -202,12 +203,15 @@ func TestRawEncipherDecipher(t *testing.T) {
// Now that we have the ciphertext (mapped to the mnemonic), we'll // Now that we have the ciphertext (mapped to the mnemonic), we'll
// attempt to decipher it raw using the user's passphrase. // attempt to decipher it raw using the user's passphrase.
plainSeedBytes, err := mnemonic.Decipher(pass) plainSeedBytes, salt, err := mnemonic.Decipher(pass)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, cipherSeed.salt, salt)
// If we deserialize the plaintext seed bytes, it should exactly match // If we deserialize the plaintext seed bytes, it should exactly match
// the original cipher seed. // the original cipher seed.
var newSeed CipherSeed newSeed := CipherSeed{
salt: salt,
}
err = newSeed.decode(bytes.NewReader(plainSeedBytes[:])) err = newSeed.decode(bytes.NewReader(plainSeedBytes[:]))
require.NoError(t, err) require.NoError(t, err)
@@ -236,7 +240,7 @@ func TestInvalidExternalVersion(t *testing.T) {
// With the version swapped, if we try to decipher it, (no matter the // With the version swapped, if we try to decipher it, (no matter the
// passphrase), it should fail. // passphrase), it should fail.
_, err = decipherCipherSeed(cipherText, []byte("kek")) _, _, err = decipherCipherSeed(cipherText, []byte("kek"))
require.Equal(t, ErrIncorrectVersion, err) require.Equal(t, ErrIncorrectVersion, err)
} }