1 package org.bouncycastle.crypto.engines; 2 3 4 import java.io.ByteArrayInputStream; 5 import java.io.IOException; 6 import java.math.BigInteger; 7 8 import org.bouncycastle.crypto.BasicAgreement; 9 import org.bouncycastle.crypto.BufferedBlockCipher; 10 import org.bouncycastle.crypto.CipherParameters; 11 import org.bouncycastle.crypto.DataLengthException; 12 import org.bouncycastle.crypto.DerivationFunction; 13 import org.bouncycastle.crypto.DerivationParameters; 14 import org.bouncycastle.crypto.Digest; 15 import org.bouncycastle.crypto.DigestDerivationFunction; 16 import org.bouncycastle.crypto.EphemeralKeyPair; 17 import org.bouncycastle.crypto.InvalidCipherTextException; 18 import org.bouncycastle.crypto.KeyParser; 19 import org.bouncycastle.crypto.Mac; 20 import org.bouncycastle.crypto.OutputLengthException; 21 import org.bouncycastle.crypto.digests.SHA256Digest; 22 import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator; 23 import org.bouncycastle.crypto.params.AsymmetricKeyParameter; 24 import org.bouncycastle.crypto.params.IESParameters; 25 import org.bouncycastle.crypto.params.IESWithCipherParameters; 26 import org.bouncycastle.crypto.params.ISO18033KDFParameters; 27 import org.bouncycastle.crypto.params.KDFParameters; 28 import org.bouncycastle.crypto.params.KeyParameter; 29 import org.bouncycastle.crypto.params.ParametersWithIV; 30 import org.bouncycastle.util.Arrays; 31 import org.bouncycastle.util.BigIntegers; 32 import org.bouncycastle.util.Pack; 33 34 /** 35 * Support class for constructing integrated encryption ciphers for doing basic message exchanges on top of key 36 * agreement ciphers. Follows the description given in IEEE Std 1363a. 37 * <p> 38 * Some tweaks added to IESEngine to conform to the Ethereum encryption approach. 39 */ 40 public class EthereumIESEngine 41 { 42 BasicAgreement agree; 43 DerivationFunction kdf; 44 Mac mac; 45 BufferedBlockCipher cipher; 46 byte[] macBuf; 47 // Ethereum addition: commonMac added when performing the MAC encryption. 48 byte[] commonMac; 49 50 boolean forEncryption; 51 CipherParameters privParam, pubParam; 52 IESParameters param; 53 54 byte[] V; 55 private EphemeralKeyPairGenerator keyPairGenerator; 56 private KeyParser keyParser; 57 private byte[] IV; 58 59 /** 60 * Set up for use with stream mode, where the key derivation function is used to provide a stream of bytes to xor with 61 * the message. 62 * 63 * @param agree the key agreement used as the basis for the encryption 64 * @param kdf the key derivation function used for byte generation 65 * @param mac the message authentication code generator for the message 66 * @param commonMac the common MAC bytes to append to the mac 67 */ EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac)68 public EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac) 69 { 70 this.agree = agree; 71 this.kdf = kdf; 72 this.mac = mac; 73 this.macBuf = new byte[mac.getMacSize()]; 74 this.commonMac = commonMac; 75 this.cipher = null; 76 } 77 78 /** 79 * Set up for use in conjunction with a block cipher to handle the message. It is <b>strongly</b> recommended that the 80 * cipher is not in ECB mode. 81 * 82 * @param agree the key agreement used as the basis for the encryption 83 * @param kdf the key derivation function used for byte generation 84 * @param mac the message authentication code generator for the message 85 * @param commonMac the common MAC bytes to append to the mac 86 * @param cipher the cipher to used for encrypting the message 87 */ EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac, BufferedBlockCipher cipher)88 public EthereumIESEngine(BasicAgreement agree, DerivationFunction kdf, Mac mac, byte[] commonMac, BufferedBlockCipher cipher) 89 { 90 this.agree = agree; 91 this.kdf = kdf; 92 this.mac = mac; 93 this.macBuf = new byte[mac.getMacSize()]; 94 this.commonMac = commonMac; 95 this.cipher = cipher; 96 } 97 98 /** 99 * Initialise the encryptor. 100 * 101 * @param forEncryption whether or not this is encryption/decryption. 102 * @param privParam our private key parameters 103 * @param pubParam the recipient's/sender's public key parameters 104 * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. 105 */ init( boolean forEncryption, CipherParameters privParam, CipherParameters pubParam, CipherParameters params)106 public void init( 107 boolean forEncryption, 108 CipherParameters privParam, 109 CipherParameters pubParam, 110 CipherParameters params) 111 { 112 this.forEncryption = forEncryption; 113 this.privParam = privParam; 114 this.pubParam = pubParam; 115 this.V = new byte[0]; 116 117 extractParams(params); 118 } 119 120 /** 121 * Initialise the decryptor. 122 * 123 * @param publicKey the recipient's/sender's public key parameters 124 * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. 125 * @param ephemeralKeyPairGenerator the ephemeral key pair generator to use. 126 */ init( AsymmetricKeyParameter publicKey, CipherParameters params, EphemeralKeyPairGenerator ephemeralKeyPairGenerator)127 public void init( 128 AsymmetricKeyParameter publicKey, 129 CipherParameters params, 130 EphemeralKeyPairGenerator ephemeralKeyPairGenerator) 131 { 132 this.forEncryption = true; 133 this.pubParam = publicKey; 134 this.keyPairGenerator = ephemeralKeyPairGenerator; 135 136 extractParams(params); 137 } 138 139 /** 140 * Initialise the encryptor. 141 * 142 * @param privateKey the recipient's private key. 143 * @param params encoding and derivation parameters, may be wrapped to include an IV for an underlying block cipher. 144 * @param publicKeyParser the parser for reading the ephemeral public key. 145 */ init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser)146 public void init(AsymmetricKeyParameter privateKey, CipherParameters params, KeyParser publicKeyParser) 147 { 148 this.forEncryption = false; 149 this.privParam = privateKey; 150 this.keyParser = publicKeyParser; 151 152 extractParams(params); 153 } 154 extractParams(CipherParameters params)155 private void extractParams(CipherParameters params) 156 { 157 if (params instanceof ParametersWithIV) 158 { 159 this.IV = ((ParametersWithIV)params).getIV(); 160 this.param = (IESParameters)((ParametersWithIV)params).getParameters(); 161 } 162 else 163 { 164 this.IV = null; 165 this.param = (IESParameters)params; 166 } 167 } 168 getCipher()169 public BufferedBlockCipher getCipher() 170 { 171 return cipher; 172 } 173 getMac()174 public Mac getMac() 175 { 176 return mac; 177 } 178 encryptBlock(byte[] in, int inOff, int inLen)179 private byte[] encryptBlock(byte[] in, int inOff, int inLen) 180 throws InvalidCipherTextException 181 { 182 byte[] C = null, K = null, K1 = null, K2 = null; 183 int len; 184 185 if (cipher == null) 186 { 187 // Streaming mode. 188 K1 = new byte[inLen]; 189 K2 = new byte[param.getMacKeySize() / 8]; 190 K = new byte[K1.length + K2.length]; 191 192 kdf.generateBytes(K, 0, K.length); 193 194 if (V.length != 0) 195 { 196 System.arraycopy(K, 0, K2, 0, K2.length); 197 System.arraycopy(K, K2.length, K1, 0, K1.length); 198 } 199 else 200 { 201 System.arraycopy(K, 0, K1, 0, K1.length); 202 System.arraycopy(K, inLen, K2, 0, K2.length); 203 } 204 205 C = new byte[inLen]; 206 207 for (int i = 0; i != inLen; i++) 208 { 209 C[i] = (byte)(in[inOff + i] ^ K1[i]); 210 } 211 len = inLen; 212 } 213 else 214 { 215 // Block cipher mode. 216 K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; 217 K2 = new byte[param.getMacKeySize() / 8]; 218 K = new byte[K1.length + K2.length]; 219 220 kdf.generateBytes(K, 0, K.length); 221 System.arraycopy(K, 0, K1, 0, K1.length); 222 System.arraycopy(K, K1.length, K2, 0, K2.length); 223 224 // If iv provided use it to initialise the cipher 225 if (IV != null) 226 { 227 cipher.init(true, new ParametersWithIV(new KeyParameter(K1), IV)); 228 } 229 else 230 { 231 cipher.init(true, new KeyParameter(K1)); 232 } 233 234 C = new byte[cipher.getOutputSize(inLen)]; 235 len = cipher.processBytes(in, inOff, inLen, C, 0); 236 len += cipher.doFinal(C, len); 237 } 238 239 240 // Convert the length of the encoding vector into a byte array. 241 byte[] P2 = param.getEncodingV(); 242 byte[] L2 = null; 243 if (V.length != 0) 244 { 245 L2 = getLengthTag(P2); 246 } 247 248 249 // Apply the MAC. 250 byte[] T = new byte[mac.getMacSize()]; 251 // Ethereum change: 252 // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. 253 // Old code: mac.init(new KeyParameter(K2)); 254 Digest hash = new SHA256Digest(); 255 byte[] K2hash = new byte[hash.getDigestSize()]; 256 hash.reset(); 257 hash.update(K2, 0, K2.length); 258 hash.doFinal(K2hash, 0); 259 260 mac.init(new KeyParameter(K2hash)); 261 // we also update the mac with the IV: 262 mac.update(IV, 0, IV.length); 263 // end of Ethereum change. 264 mac.update(C, 0, C.length); 265 if (P2 != null) 266 { 267 mac.update(P2, 0, P2.length); 268 } 269 if (V.length != 0) 270 { 271 mac.update(L2, 0, L2.length); 272 } 273 // Ethereum change 274 mac.update(commonMac, 0, commonMac.length); 275 mac.doFinal(T, 0); 276 277 278 // Output the triple (V,C,T). 279 byte[] Output = new byte[V.length + len + T.length]; 280 System.arraycopy(V, 0, Output, 0, V.length); 281 System.arraycopy(C, 0, Output, V.length, len); 282 System.arraycopy(T, 0, Output, V.length + len, T.length); 283 return Output; 284 } 285 decryptBlock(byte[] in_enc, int inOff, int inLen)286 private byte[] decryptBlock(byte[] in_enc, int inOff, int inLen) 287 throws InvalidCipherTextException 288 { 289 byte[] M, K, K1, K2; 290 int len = 0; 291 292 // Ensure that the length of the input is greater than the MAC in bytes 293 if (inLen < V.length + mac.getMacSize()) 294 { 295 throw new InvalidCipherTextException("length of input must be greater than the MAC and V combined"); 296 } 297 298 // note order is important: set up keys, do simple encryptions, check mac, do final encryption. 299 if (cipher == null) 300 { 301 // Streaming mode. 302 K1 = new byte[inLen - V.length - mac.getMacSize()]; 303 K2 = new byte[param.getMacKeySize() / 8]; 304 K = new byte[K1.length + K2.length]; 305 306 kdf.generateBytes(K, 0, K.length); 307 308 if (V.length != 0) 309 { 310 System.arraycopy(K, 0, K2, 0, K2.length); 311 System.arraycopy(K, K2.length, K1, 0, K1.length); 312 } 313 else 314 { 315 System.arraycopy(K, 0, K1, 0, K1.length); 316 System.arraycopy(K, K1.length, K2, 0, K2.length); 317 } 318 319 // process the message 320 M = new byte[K1.length]; 321 322 for (int i = 0; i != K1.length; i++) 323 { 324 M[i] = (byte)(in_enc[inOff + V.length + i] ^ K1[i]); 325 } 326 } 327 else 328 { 329 // Block cipher mode. 330 K1 = new byte[((IESWithCipherParameters)param).getCipherKeySize() / 8]; 331 K2 = new byte[param.getMacKeySize() / 8]; 332 K = new byte[K1.length + K2.length]; 333 334 kdf.generateBytes(K, 0, K.length); 335 System.arraycopy(K, 0, K1, 0, K1.length); 336 System.arraycopy(K, K1.length, K2, 0, K2.length); 337 338 CipherParameters cp = new KeyParameter(K1); 339 340 // If IV provide use it to initialize the cipher 341 if (IV != null) 342 { 343 cp = new ParametersWithIV(cp, IV); 344 } 345 346 cipher.init(false, cp); 347 348 M = new byte[cipher.getOutputSize(inLen - V.length - mac.getMacSize())]; 349 350 // do initial processing 351 len = cipher.processBytes(in_enc, inOff + V.length, inLen - V.length - mac.getMacSize(), M, 0); 352 } 353 354 // Convert the length of the encoding vector into a byte array. 355 byte[] P2 = param.getEncodingV(); 356 byte[] L2 = null; 357 if (V.length != 0) 358 { 359 L2 = getLengthTag(P2); 360 } 361 362 // Verify the MAC. 363 int end = inOff + inLen; 364 byte[] T1 = Arrays.copyOfRange(in_enc, end - mac.getMacSize(), end); 365 366 byte[] T2 = new byte[T1.length]; 367 // Ethereum change: 368 // Instead of initializing the mac with the bytes, we initialize with the hash of the bytes. 369 // Old code: mac.init(new KeyParameter(K2)); 370 Digest hash = new SHA256Digest(); 371 byte[] K2hash = new byte[hash.getDigestSize()]; 372 hash.reset(); 373 hash.update(K2, 0, K2.length); 374 hash.doFinal(K2hash, 0); 375 mac.init(new KeyParameter(K2hash)); 376 // we also update the mac with the IV: 377 mac.update(IV, 0, IV.length); 378 // end of Ethereum change. 379 380 mac.update(in_enc, inOff + V.length, inLen - V.length - T2.length); 381 382 if (P2 != null) 383 { 384 mac.update(P2, 0, P2.length); 385 } 386 if (V.length != 0) 387 { 388 mac.update(L2, 0, L2.length); 389 } 390 // Ethereum change 391 mac.update(commonMac, 0, commonMac.length); 392 mac.doFinal(T2, 0); 393 394 if (!Arrays.constantTimeAreEqual(T1, T2)) 395 { 396 throw new InvalidCipherTextException("invalid MAC"); 397 } 398 399 if (cipher == null) 400 { 401 return M; 402 } 403 else 404 { 405 len += cipher.doFinal(M, len); 406 407 return Arrays.copyOfRange(M, 0, len); 408 } 409 } 410 411 processBlock(byte[] in, int inOff, int inLen)412 public byte[] processBlock(byte[] in, int inOff, int inLen) 413 throws InvalidCipherTextException 414 { 415 if (forEncryption) 416 { 417 if (keyPairGenerator != null) 418 { 419 EphemeralKeyPair ephKeyPair = keyPairGenerator.generate(); 420 421 this.privParam = ephKeyPair.getKeyPair().getPrivate(); 422 this.V = ephKeyPair.getEncodedPublicKey(); 423 } 424 } 425 else 426 { 427 if (keyParser != null) 428 { 429 ByteArrayInputStream bIn = new ByteArrayInputStream(in, inOff, inLen); 430 431 try 432 { 433 this.pubParam = keyParser.readKey(bIn); 434 } 435 catch (IOException e) 436 { 437 throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); 438 } 439 catch (IllegalArgumentException e) 440 { 441 throw new InvalidCipherTextException("unable to recover ephemeral public key: " + e.getMessage(), e); 442 } 443 444 int encLength = (inLen - bIn.available()); 445 this.V = Arrays.copyOfRange(in, inOff, inOff + encLength); 446 } 447 } 448 449 // Compute the common value and convert to byte array. 450 agree.init(privParam); 451 BigInteger z = agree.calculateAgreement(pubParam); 452 byte[] Z = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), z); 453 454 // Create input to KDF. 455 if (V.length != 0) 456 { 457 byte[] VZ = Arrays.concatenate(V, Z); 458 Arrays.fill(Z, (byte)0); 459 Z = VZ; 460 } 461 462 try 463 { 464 // Initialise the KDF. 465 KDFParameters kdfParam = new KDFParameters(Z, param.getDerivationV()); 466 kdf.init(kdfParam); 467 468 return forEncryption ? encryptBlock(in, inOff, inLen) : decryptBlock(in, inOff, inLen); 469 } 470 finally 471 { 472 Arrays.fill(Z, (byte)0); 473 } 474 } 475 476 // as described in Shroup's paper and P1363a getLengthTag(byte[] p2)477 protected byte[] getLengthTag(byte[] p2) 478 { 479 byte[] L2 = new byte[8]; 480 if (p2 != null) 481 { 482 Pack.longToBigEndian(p2.length * 8L, L2, 0); 483 } 484 return L2; 485 } 486 487 /** 488 * Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 <br> 489 * This implementation is based on ISO 18033/P1363a. 490 * <p> 491 * This class has been adapted from the <tt>BaseKDFBytesGenerator</tt> implementation of Bouncy Castle. Only one 492 * change is present specifically for Ethereum. 493 */ 494 public static class HandshakeKDFFunction 495 implements DigestDerivationFunction 496 { 497 private int counterStart; 498 private Digest digest; 499 private byte[] shared; 500 private byte[] iv; 501 502 /** 503 * Construct a KDF Parameters generator. 504 * <p> 505 * 506 * @param counterStart value of counter. 507 * @param digest the digest to be used as the source of derived keys. 508 */ HandshakeKDFFunction(int counterStart, Digest digest)509 public HandshakeKDFFunction(int counterStart, Digest digest) 510 { 511 this.counterStart = counterStart; 512 this.digest = digest; 513 } 514 init(DerivationParameters param)515 public void init(DerivationParameters param) 516 { 517 if (param instanceof KDFParameters) 518 { 519 KDFParameters p = (KDFParameters)param; 520 521 shared = p.getSharedSecret(); 522 iv = p.getIV(); 523 } 524 else if (param instanceof ISO18033KDFParameters) 525 { 526 ISO18033KDFParameters p = (ISO18033KDFParameters)param; 527 528 shared = p.getSeed(); 529 iv = null; 530 } 531 else 532 { 533 throw new IllegalArgumentException("KDF parameters required for generator"); 534 } 535 } 536 537 /** 538 * return the underlying digest. 539 */ getDigest()540 public Digest getDigest() 541 { 542 return digest; 543 } 544 545 /** 546 * fill len bytes of the output buffer with bytes generated from the derivation function. 547 * 548 * @throws IllegalArgumentException if the size of the request will cause an overflow. 549 * @throws DataLengthException if the out buffer is too small. 550 */ generateBytes(byte[] out, int outOff, int len)551 public int generateBytes(byte[] out, int outOff, int len) 552 throws DataLengthException, IllegalArgumentException 553 { 554 if ((out.length - len) < outOff) 555 { 556 throw new OutputLengthException("output buffer too small"); 557 } 558 559 long oBytes = len; 560 int outLen = digest.getDigestSize(); 561 562 // 563 // this is at odds with the standard implementation, the 564 // maximum value should be hBits * (2^32 - 1) where hBits 565 // is the digest output size in bits. We can't have an 566 // array with a long index at the moment... 567 // 568 if (oBytes > ((2L << 32) - 1)) 569 { 570 throw new IllegalArgumentException("output length too large"); 571 } 572 573 int cThreshold = (int)((oBytes + outLen - 1) / outLen); 574 575 byte[] dig = new byte[digest.getDigestSize()]; 576 577 byte[] C = new byte[4]; 578 Pack.intToBigEndian(counterStart, C, 0); 579 580 int counterBase = counterStart & ~0xFF; 581 582 for (int i = 0; i < cThreshold; i++) 583 { 584 // only change for Ethereum: invert those 2 lines. 585 digest.update(C, 0, C.length); 586 digest.update(shared, 0, shared.length); 587 // End of change for Ethereum. 588 589 if (iv != null) 590 { 591 digest.update(iv, 0, iv.length); 592 } 593 594 digest.doFinal(dig, 0); 595 596 if (len > outLen) 597 { 598 System.arraycopy(dig, 0, out, outOff, outLen); 599 outOff += outLen; 600 len -= outLen; 601 } 602 else 603 { 604 System.arraycopy(dig, 0, out, outOff, len); 605 } 606 607 if (++C[3] == 0) 608 { 609 counterBase += 0x100; 610 Pack.intToBigEndian(counterBase, C, 0); 611 } 612 } 613 614 digest.reset(); 615 616 return (int)oBytes; 617 } 618 } 619 } 620