1 package org.bouncycastle.crypto.modes; 2 3 import org.bouncycastle.crypto.BlockCipher; 4 import org.bouncycastle.crypto.CipherParameters; 5 import org.bouncycastle.crypto.DataLengthException; 6 import org.bouncycastle.crypto.InvalidCipherTextException; 7 import org.bouncycastle.crypto.OutputLengthException; 8 import org.bouncycastle.crypto.modes.gcm.GCMExponentiator; 9 import org.bouncycastle.crypto.modes.gcm.GCMMultiplier; 10 import org.bouncycastle.crypto.modes.gcm.GCMUtil; 11 import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator; 12 import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier; 13 import org.bouncycastle.crypto.params.AEADParameters; 14 import org.bouncycastle.crypto.params.KeyParameter; 15 import org.bouncycastle.crypto.params.ParametersWithIV; 16 import org.bouncycastle.util.Arrays; 17 import org.bouncycastle.util.Pack; 18 19 /** 20 * Implements the Galois/Counter mode (GCM) detailed in 21 * NIST Special Publication 800-38D. 22 */ 23 public class GCMBlockCipher 24 implements AEADBlockCipher 25 { 26 private static final int BLOCK_SIZE = 16; 27 // BEGIN android-added 28 // 2^36-32 : limitation imposed by NIST GCM as otherwise the counter is wrapped and it can leak 29 // plaintext and authentication key 30 private static final long MAX_INPUT_SIZE = 68719476704L; 31 // END android-added 32 33 // not final due to a compiler bug 34 private BlockCipher cipher; 35 private GCMMultiplier multiplier; 36 private GCMExponentiator exp; 37 38 // These fields are set by init and not modified by processing 39 private boolean forEncryption; 40 private boolean initialised; 41 private int macSize; 42 private byte[] lastKey; 43 private byte[] nonce; 44 private byte[] initialAssociatedText; 45 private byte[] H; 46 private byte[] J0; 47 48 // These fields are modified during processing 49 private byte[] bufBlock; 50 private byte[] macBlock; 51 private byte[] S, S_at, S_atPre; 52 private byte[] counter; 53 private int blocksRemaining; 54 private int bufOff; 55 private long totalLength; 56 private byte[] atBlock; 57 private int atBlockPos; 58 private long atLength; 59 private long atLengthPre; 60 GCMBlockCipher(BlockCipher c)61 public GCMBlockCipher(BlockCipher c) 62 { 63 this(c, null); 64 } 65 GCMBlockCipher(BlockCipher c, GCMMultiplier m)66 public GCMBlockCipher(BlockCipher c, GCMMultiplier m) 67 { 68 if (c.getBlockSize() != BLOCK_SIZE) 69 { 70 throw new IllegalArgumentException( 71 "cipher required with a block size of " + BLOCK_SIZE + "."); 72 } 73 74 if (m == null) 75 { 76 // TODO Consider a static property specifying default multiplier 77 m = new Tables8kGCMMultiplier(); 78 } 79 80 this.cipher = c; 81 this.multiplier = m; 82 } 83 getUnderlyingCipher()84 public BlockCipher getUnderlyingCipher() 85 { 86 return cipher; 87 } 88 getAlgorithmName()89 public String getAlgorithmName() 90 { 91 return cipher.getAlgorithmName() + "/GCM"; 92 } 93 94 /** 95 * NOTE: MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default is 128 bits. 96 * Sizes less than 96 are not recommended, but are supported for specialized applications. 97 */ init(boolean forEncryption, CipherParameters params)98 public void init(boolean forEncryption, CipherParameters params) 99 throws IllegalArgumentException 100 { 101 this.forEncryption = forEncryption; 102 this.macBlock = null; 103 this.initialised = true; 104 105 KeyParameter keyParam; 106 byte[] newNonce = null; 107 108 if (params instanceof AEADParameters) 109 { 110 AEADParameters param = (AEADParameters)params; 111 112 newNonce = param.getNonce(); 113 initialAssociatedText = param.getAssociatedText(); 114 115 int macSizeBits = param.getMacSize(); 116 if (macSizeBits < 32 || macSizeBits > 128 || macSizeBits % 8 != 0) 117 { 118 throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); 119 } 120 121 macSize = macSizeBits / 8; 122 keyParam = param.getKey(); 123 } 124 else if (params instanceof ParametersWithIV) 125 { 126 ParametersWithIV param = (ParametersWithIV)params; 127 128 newNonce = param.getIV(); 129 initialAssociatedText = null; 130 macSize = 16; 131 keyParam = (KeyParameter)param.getParameters(); 132 } 133 else 134 { 135 throw new IllegalArgumentException("invalid parameters passed to GCM"); 136 } 137 138 int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize); 139 this.bufBlock = new byte[bufLength]; 140 141 if (newNonce == null || newNonce.length < 1) 142 { 143 throw new IllegalArgumentException("IV must be at least 1 byte"); 144 } 145 146 if (forEncryption) 147 { 148 if (nonce != null && Arrays.areEqual(nonce, newNonce)) 149 { 150 if (keyParam == null) 151 { 152 throw new IllegalArgumentException("cannot reuse nonce for GCM encryption"); 153 } 154 if (lastKey != null && Arrays.areEqual(lastKey, keyParam.getKey())) 155 { 156 throw new IllegalArgumentException("cannot reuse nonce for GCM encryption"); 157 } 158 } 159 } 160 161 nonce = newNonce; 162 if (keyParam != null) 163 { 164 lastKey = keyParam.getKey(); 165 } 166 167 // TODO Restrict macSize to 16 if nonce length not 12? 168 169 // Cipher always used in forward mode 170 // if keyParam is null we're reusing the last key. 171 if (keyParam != null) 172 { 173 cipher.init(true, keyParam); 174 175 this.H = new byte[BLOCK_SIZE]; 176 cipher.processBlock(H, 0, H, 0); 177 178 // GCMMultiplier tables don't change unless the key changes (and are expensive to init) 179 multiplier.init(H); 180 exp = null; 181 } 182 else if (this.H == null) 183 { 184 throw new IllegalArgumentException("Key must be specified in initial init"); 185 } 186 187 this.J0 = new byte[BLOCK_SIZE]; 188 189 if (nonce.length == 12) 190 { 191 System.arraycopy(nonce, 0, J0, 0, nonce.length); 192 this.J0[BLOCK_SIZE - 1] = 0x01; 193 } 194 else 195 { 196 gHASH(J0, nonce, nonce.length); 197 byte[] X = new byte[BLOCK_SIZE]; 198 Pack.longToBigEndian((long)nonce.length * 8, X, 8); 199 gHASHBlock(J0, X); 200 } 201 202 this.S = new byte[BLOCK_SIZE]; 203 this.S_at = new byte[BLOCK_SIZE]; 204 this.S_atPre = new byte[BLOCK_SIZE]; 205 this.atBlock = new byte[BLOCK_SIZE]; 206 this.atBlockPos = 0; 207 this.atLength = 0; 208 this.atLengthPre = 0; 209 this.counter = Arrays.clone(J0); 210 this.blocksRemaining = -2; // page 8, len(P) <= 2^39 - 256, 1 block used by tag but done on J0 211 this.bufOff = 0; 212 this.totalLength = 0; 213 214 if (initialAssociatedText != null) 215 { 216 processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); 217 } 218 } 219 getMac()220 public byte[] getMac() 221 { 222 if (macBlock == null) 223 { 224 return new byte[macSize]; 225 } 226 return Arrays.clone(macBlock); 227 } 228 getOutputSize(int len)229 public int getOutputSize(int len) 230 { 231 int totalData = len + bufOff; 232 233 if (forEncryption) 234 { 235 return totalData + macSize; 236 } 237 238 return totalData < macSize ? 0 : totalData - macSize; 239 } 240 241 // BEGIN android-added 242 /** Helper used to ensure that {@link #MAX_INPUT_SIZE} is not exceeded. */ getTotalInputSizeAfterNewInput(int newInputLen)243 private long getTotalInputSizeAfterNewInput(int newInputLen) 244 { 245 return totalLength + newInputLen + bufOff; 246 } 247 // END android-added 248 getUpdateOutputSize(int len)249 public int getUpdateOutputSize(int len) 250 { 251 int totalData = len + bufOff; 252 if (!forEncryption) 253 { 254 if (totalData < macSize) 255 { 256 return 0; 257 } 258 totalData -= macSize; 259 } 260 return totalData - totalData % BLOCK_SIZE; 261 } 262 processAADByte(byte in)263 public void processAADByte(byte in) 264 { 265 checkStatus(); 266 // BEGIN android-added 267 if (getTotalInputSizeAfterNewInput(1) > MAX_INPUT_SIZE) { 268 throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes"); 269 } 270 // END android-added 271 atBlock[atBlockPos] = in; 272 if (++atBlockPos == BLOCK_SIZE) 273 { 274 // Hash each block as it fills 275 gHASHBlock(S_at, atBlock); 276 atBlockPos = 0; 277 atLength += BLOCK_SIZE; 278 } 279 } 280 processAADBytes(byte[] in, int inOff, int len)281 public void processAADBytes(byte[] in, int inOff, int len) 282 { 283 // BEGIN android-added 284 if (getTotalInputSizeAfterNewInput(len) > MAX_INPUT_SIZE) { 285 throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes"); 286 } 287 // END android-added 288 for (int i = 0; i < len; ++i) 289 { 290 atBlock[atBlockPos] = in[inOff + i]; 291 if (++atBlockPos == BLOCK_SIZE) 292 { 293 // Hash each block as it fills 294 gHASHBlock(S_at, atBlock); 295 atBlockPos = 0; 296 atLength += BLOCK_SIZE; 297 } 298 } 299 } 300 initCipher()301 private void initCipher() 302 { 303 if (atLength > 0) 304 { 305 System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE); 306 atLengthPre = atLength; 307 } 308 309 // Finish hash for partial AAD block 310 if (atBlockPos > 0) 311 { 312 gHASHPartial(S_atPre, atBlock, 0, atBlockPos); 313 atLengthPre += atBlockPos; 314 } 315 316 if (atLengthPre > 0) 317 { 318 System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE); 319 } 320 } 321 processByte(byte in, byte[] out, int outOff)322 public int processByte(byte in, byte[] out, int outOff) 323 throws DataLengthException 324 { 325 checkStatus(); 326 // BEGIN android-added 327 if (getTotalInputSizeAfterNewInput(1) > MAX_INPUT_SIZE) { 328 throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes"); 329 } 330 // END android-added 331 332 bufBlock[bufOff] = in; 333 if (++bufOff == bufBlock.length) 334 { 335 outputBlock(out, outOff); 336 return BLOCK_SIZE; 337 } 338 return 0; 339 } 340 processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)341 public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) 342 throws DataLengthException 343 { 344 checkStatus(); 345 // BEGIN android-added 346 if (getTotalInputSizeAfterNewInput(len) > MAX_INPUT_SIZE) { 347 throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes"); 348 } 349 // END android-added 350 351 if (in.length < (inOff + len)) 352 { 353 throw new DataLengthException("Input buffer too short"); 354 } 355 int resultLen = 0; 356 357 for (int i = 0; i < len; ++i) 358 { 359 bufBlock[bufOff] = in[inOff + i]; 360 if (++bufOff == bufBlock.length) 361 { 362 outputBlock(out, outOff + resultLen); 363 resultLen += BLOCK_SIZE; 364 } 365 } 366 367 return resultLen; 368 } 369 outputBlock(byte[] output, int offset)370 private void outputBlock(byte[] output, int offset) 371 { 372 if (output.length < (offset + BLOCK_SIZE)) 373 { 374 throw new OutputLengthException("Output buffer too short"); 375 } 376 if (totalLength == 0) 377 { 378 initCipher(); 379 } 380 gCTRBlock(bufBlock, output, offset); 381 if (forEncryption) 382 { 383 bufOff = 0; 384 } 385 else 386 { 387 System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); 388 bufOff = macSize; 389 } 390 } 391 doFinal(byte[] out, int outOff)392 public int doFinal(byte[] out, int outOff) 393 throws IllegalStateException, InvalidCipherTextException 394 { 395 checkStatus(); 396 397 if (totalLength == 0) 398 { 399 initCipher(); 400 } 401 402 int extra = bufOff; 403 404 if (forEncryption) 405 { 406 if (out.length < (outOff + extra + macSize)) 407 { 408 throw new OutputLengthException("Output buffer too short"); 409 } 410 } 411 else 412 { 413 if (extra < macSize) 414 { 415 throw new InvalidCipherTextException("data too short"); 416 } 417 extra -= macSize; 418 419 if (out.length < (outOff + extra)) 420 { 421 throw new OutputLengthException("Output buffer too short"); 422 } 423 } 424 425 if (extra > 0) 426 { 427 gCTRPartial(bufBlock, 0, extra, out, outOff); 428 } 429 430 atLength += atBlockPos; 431 432 if (atLength > atLengthPre) 433 { 434 /* 435 * Some AAD was sent after the cipher started. We determine the difference b/w the hash value 436 * we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at). 437 * Then we carry this difference forward by multiplying by H^c, where c is the number of (full or 438 * partial) cipher-text blocks produced, and adjust the current hash. 439 */ 440 441 // Finish hash for partial AAD block 442 if (atBlockPos > 0) 443 { 444 gHASHPartial(S_at, atBlock, 0, atBlockPos); 445 } 446 447 // Find the difference between the AAD hashes 448 if (atLengthPre > 0) 449 { 450 GCMUtil.xor(S_at, S_atPre); 451 } 452 453 // Number of cipher-text blocks produced 454 long c = ((totalLength * 8) + 127) >>> 7; 455 456 // Calculate the adjustment factor 457 byte[] H_c = new byte[16]; 458 if (exp == null) 459 { 460 exp = new Tables1kGCMExponentiator(); 461 exp.init(H); 462 } 463 exp.exponentiateX(c, H_c); 464 465 // Carry the difference forward 466 GCMUtil.multiply(S_at, H_c); 467 468 // Adjust the current hash 469 GCMUtil.xor(S, S_at); 470 } 471 472 // Final gHASH 473 byte[] X = new byte[BLOCK_SIZE]; 474 Pack.longToBigEndian(atLength * 8, X, 0); 475 Pack.longToBigEndian(totalLength * 8, X, 8); 476 477 gHASHBlock(S, X); 478 479 // T = MSBt(GCTRk(J0,S)) 480 byte[] tag = new byte[BLOCK_SIZE]; 481 cipher.processBlock(J0, 0, tag, 0); 482 GCMUtil.xor(tag, S); 483 484 int resultLen = extra; 485 486 // We place into macBlock our calculated value for T 487 this.macBlock = new byte[macSize]; 488 System.arraycopy(tag, 0, macBlock, 0, macSize); 489 490 if (forEncryption) 491 { 492 // Append T to the message 493 System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize); 494 resultLen += macSize; 495 } 496 else 497 { 498 // Retrieve the T value from the message and compare to calculated one 499 byte[] msgMac = new byte[macSize]; 500 System.arraycopy(bufBlock, extra, msgMac, 0, macSize); 501 if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac)) 502 { 503 throw new InvalidCipherTextException("mac check in GCM failed"); 504 } 505 } 506 507 reset(false); 508 509 return resultLen; 510 } 511 reset()512 public void reset() 513 { 514 reset(true); 515 } 516 reset( boolean clearMac)517 private void reset( 518 boolean clearMac) 519 { 520 cipher.reset(); 521 522 // note: we do not reset the nonce. 523 524 S = new byte[BLOCK_SIZE]; 525 S_at = new byte[BLOCK_SIZE]; 526 S_atPre = new byte[BLOCK_SIZE]; 527 atBlock = new byte[BLOCK_SIZE]; 528 atBlockPos = 0; 529 atLength = 0; 530 atLengthPre = 0; 531 counter = Arrays.clone(J0); 532 blocksRemaining = -2; 533 bufOff = 0; 534 totalLength = 0; 535 536 if (bufBlock != null) 537 { 538 Arrays.fill(bufBlock, (byte)0); 539 } 540 541 if (clearMac) 542 { 543 macBlock = null; 544 } 545 546 if (forEncryption) 547 { 548 initialised = false; 549 } 550 else 551 { 552 if (initialAssociatedText != null) 553 { 554 processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); 555 } 556 } 557 } 558 gCTRBlock(byte[] block, byte[] out, int outOff)559 private void gCTRBlock(byte[] block, byte[] out, int outOff) 560 { 561 byte[] tmp = getNextCounterBlock(); 562 563 GCMUtil.xor(tmp, block); 564 System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE); 565 566 gHASHBlock(S, forEncryption ? tmp : block); 567 568 totalLength += BLOCK_SIZE; 569 } 570 gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff)571 private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff) 572 { 573 byte[] tmp = getNextCounterBlock(); 574 575 GCMUtil.xor(tmp, buf, off, len); 576 System.arraycopy(tmp, 0, out, outOff, len); 577 578 gHASHPartial(S, forEncryption ? tmp : buf, 0, len); 579 580 totalLength += len; 581 } 582 gHASH(byte[] Y, byte[] b, int len)583 private void gHASH(byte[] Y, byte[] b, int len) 584 { 585 for (int pos = 0; pos < len; pos += BLOCK_SIZE) 586 { 587 int num = Math.min(len - pos, BLOCK_SIZE); 588 gHASHPartial(Y, b, pos, num); 589 } 590 } 591 gHASHBlock(byte[] Y, byte[] b)592 private void gHASHBlock(byte[] Y, byte[] b) 593 { 594 GCMUtil.xor(Y, b); 595 multiplier.multiplyH(Y); 596 } 597 gHASHPartial(byte[] Y, byte[] b, int off, int len)598 private void gHASHPartial(byte[] Y, byte[] b, int off, int len) 599 { 600 GCMUtil.xor(Y, b, off, len); 601 multiplier.multiplyH(Y); 602 } 603 getNextCounterBlock()604 private byte[] getNextCounterBlock() 605 { 606 if (blocksRemaining == 0) 607 { 608 throw new IllegalStateException("Attempt to process too many blocks"); 609 } 610 blocksRemaining--; 611 612 int c = 1; 613 c += counter[15] & 0xFF; counter[15] = (byte)c; c >>>= 8; 614 c += counter[14] & 0xFF; counter[14] = (byte)c; c >>>= 8; 615 c += counter[13] & 0xFF; counter[13] = (byte)c; c >>>= 8; 616 c += counter[12] & 0xFF; counter[12] = (byte)c; 617 618 byte[] tmp = new byte[BLOCK_SIZE]; 619 // TODO Sure would be nice if ciphers could operate on int[] 620 cipher.processBlock(counter, 0, tmp, 0); 621 return tmp; 622 } 623 checkStatus()624 private void checkStatus() 625 { 626 if (!initialised) 627 { 628 if (forEncryption) 629 { 630 throw new IllegalStateException("GCM cipher cannot be reused for encryption"); 631 } 632 throw new IllegalStateException("GCM cipher needs to be initialised"); 633 } 634 } 635 } 636