github.com/status-im/status-go@v1.1.0/eth-node/crypto/ethereum_crypto.go (about) 1 package crypto 2 3 import ( 4 "bytes" 5 "crypto/aes" 6 "crypto/cipher" 7 "crypto/hmac" 8 "crypto/sha256" 9 "fmt" 10 "io" 11 12 dr "github.com/status-im/doubleratchet" 13 "golang.org/x/crypto/hkdf" 14 15 "github.com/status-im/status-go/eth-node/crypto/ecies" 16 ) 17 18 // EthereumCrypto is an implementation of Crypto with cryptographic primitives recommended 19 // by the Double Ratchet Algorithm specification. However, some details are different, 20 // see function comments for details. 21 type EthereumCrypto struct{} 22 23 // See the Crypto interface. 24 func (c EthereumCrypto) GenerateDH() (dr.DHPair, error) { 25 keys, err := GenerateKey() 26 if err != nil { 27 return nil, err 28 } 29 30 return DHPair{ 31 PubKey: CompressPubkey(&keys.PublicKey), 32 PrvKey: FromECDSA(keys), 33 }, nil 34 35 } 36 37 // See the Crypto interface. 38 func (c EthereumCrypto) DH(dhPair dr.DHPair, dhPub dr.Key) (dr.Key, error) { 39 tmpKey := dhPair.PrivateKey() 40 privateKey, err := ToECDSA(tmpKey) 41 if err != nil { 42 return nil, err 43 } 44 45 eciesPrivate := ecies.ImportECDSA(privateKey) 46 47 publicKey, err := DecompressPubkey(dhPub) 48 if err != nil { 49 return nil, err 50 } 51 eciesPublic := ecies.ImportECDSAPublic(publicKey) 52 53 key, err := eciesPrivate.GenerateShared( 54 eciesPublic, 55 16, 56 16, 57 ) 58 if err != nil { 59 return nil, err 60 } 61 62 return key, nil 63 } 64 65 // See the Crypto interface. 66 func (c EthereumCrypto) KdfRK(rk, dhOut dr.Key) (dr.Key, dr.Key, dr.Key) { 67 var ( 68 // We can use a non-secret constant as the last argument 69 r = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL")) 70 buf = make([]byte, 96) 71 ) 72 73 rootKey := make(dr.Key, 32) 74 chainKey := make(dr.Key, 32) 75 headerKey := make(dr.Key, 32) 76 77 // The only error here is an entropy limit which won't be reached for such a short buffer. 78 _, _ = io.ReadFull(r, buf) 79 80 copy(rootKey, buf[:32]) 81 copy(chainKey, buf[32:64]) 82 copy(headerKey, buf[64:96]) 83 return rootKey, chainKey, headerKey 84 } 85 86 // See the Crypto interface. 87 func (c EthereumCrypto) KdfCK(ck dr.Key) (dr.Key, dr.Key) { 88 const ( 89 ckInput = 15 90 mkInput = 16 91 ) 92 93 chainKey := make(dr.Key, 32) 94 msgKey := make(dr.Key, 32) 95 96 h := hmac.New(sha256.New, ck) 97 98 _, _ = h.Write([]byte{ckInput}) 99 copy(chainKey, h.Sum(nil)) 100 h.Reset() 101 102 _, _ = h.Write([]byte{mkInput}) 103 copy(msgKey, h.Sum(nil)) 104 105 return chainKey, msgKey 106 } 107 108 // Encrypt uses a slightly different approach than in the algorithm specification: 109 // it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation 110 // complexity considerations. 111 func (c EthereumCrypto) Encrypt(mk dr.Key, plaintext, ad []byte) ([]byte, error) { 112 encKey, authKey, iv := c.deriveEncKeys(mk) 113 114 ciphertext := make([]byte, aes.BlockSize+len(plaintext)) 115 copy(ciphertext, iv[:]) 116 117 block, err := aes.NewCipher(encKey) 118 if err != nil { 119 return nil, err 120 } 121 122 stream := cipher.NewCTR(block, iv[:]) 123 stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) 124 125 return append(ciphertext, c.computeSignature(authKey, ciphertext, ad)...), nil 126 } 127 128 // See the Crypto interface. 129 func (c EthereumCrypto) Decrypt(mk dr.Key, authCiphertext, ad []byte) ([]byte, error) { 130 var ( 131 l = len(authCiphertext) 132 ciphertext = authCiphertext[:l-sha256.Size] 133 signature = authCiphertext[l-sha256.Size:] 134 ) 135 136 // Check the signature. 137 encKey, authKey, _ := c.deriveEncKeys(mk) 138 139 if s := c.computeSignature(authKey, ciphertext, ad); !bytes.Equal(s, signature) { 140 return nil, fmt.Errorf("invalid signature") 141 } 142 143 // Decrypt. 144 block, err := aes.NewCipher(encKey) 145 if err != nil { 146 return nil, err 147 } 148 149 stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) 150 plaintext := make([]byte, len(ciphertext[aes.BlockSize:])) 151 152 stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:]) 153 154 return plaintext, nil 155 } 156 157 // deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err). 158 func (c EthereumCrypto) deriveEncKeys(mk dr.Key) (dr.Key, dr.Key, [16]byte) { 159 // First, derive encryption and authentication key out of mk. 160 salt := make([]byte, 32) 161 var ( 162 r = hkdf.New(sha256.New, mk, salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh")) 163 buf = make([]byte, 80) 164 ) 165 166 encKey := make(dr.Key, 32) 167 authKey := make(dr.Key, 32) 168 var iv [16]byte 169 170 // The only error here is an entropy limit which won't be reached for such a short buffer. 171 _, _ = io.ReadFull(r, buf) 172 173 copy(encKey, buf[0:32]) 174 copy(authKey, buf[32:64]) 175 copy(iv[:], buf[64:80]) 176 return encKey, authKey, iv 177 } 178 179 func (c EthereumCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte { 180 h := hmac.New(sha256.New, authKey) 181 _, _ = h.Write(associatedData) 182 _, _ = h.Write(ciphertext) 183 return h.Sum(nil) 184 } 185 186 type DHPair struct { 187 PrvKey dr.Key 188 PubKey dr.Key 189 } 190 191 func (p DHPair) PrivateKey() dr.Key { 192 return p.PrvKey 193 } 194 195 func (p DHPair) PublicKey() dr.Key { 196 return p.PubKey 197 }