static void gcm_oneReadByteCorrupt() throws Exception { System.out.println("Running gcm_oneReadByteCorrupt test"); // Encrypt 100 bytes with AES/GCM/PKCS5Padding byte[] ct = encryptedText("GCM", 100); // Corrupt the encrypted message ct = corruptGCM(ct); // Create stream for decryption CipherInputStream in = getStream("GCM", ct); try { in.read(); System.out.println(" Fail. No exception thrown."); } catch (IOException e) { Throwable ec = e.getCause(); if (ec instanceof AEADBadTagException) { System.out.println(" Pass."); } else { System.out.println(" Fail: " + ec.getMessage()); throw new RuntimeException(ec); } } }
@Test public void authenticatedEncryptionMacFailureTest() throws Exception { for (String provider : new String[] {null, "BC", "SunJCE"}) { ValueEncryptorBase encryptor = ValueEncryptor.AES_GCM.getInstance(provider); byte[] plaintext = "1234567890123457".getBytes(VISIBILITY_CHARSET); byte[] key = "aabbccddaabbccddaabbccddaabbccdd".getBytes(VISIBILITY_CHARSET); byte[] ciphertext = encryptor.encrypt(key, plaintext); if (ciphertext[20] == 0) { ciphertext[20] = (byte) 1; } else { ciphertext[20] = (byte) 0; } thrown.expect(EncryptionException.class); thrown.expectCause(isA(AEADBadTagException.class)); encryptor.decrypt(key, ciphertext); } }
@SuppressWarnings("InsecureCryptoUsage") @Override public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException { int plaintextLength = ciphertext.length - ivSizeInBytes - TAG_SIZE_IN_BYTES; if (plaintextLength < 0) { throw new GeneralSecurityException("ciphertext too short"); } Cipher ecb = Cipher.getInstance("AES/ECB/NOPADDING"); ecb.init(Cipher.ENCRYPT_MODE, keySpec); byte[] n = omac(ecb, 0, ciphertext, 0, ivSizeInBytes); byte[] h = omac(ecb, 1, associatedData, 0, associatedData.length); byte[] t = omac(ecb, 2, ciphertext, ivSizeInBytes, plaintextLength); byte res = 0; int offset = ciphertext.length - TAG_SIZE_IN_BYTES; for (int i = 0; i < TAG_SIZE_IN_BYTES; i++) { res = (byte) (res | (ciphertext[offset + i] ^ h[i] ^ n[i] ^ t[i])); } if (res != 0) { throw new AEADBadTagException("tag mismatch"); } Cipher ctr = Cipher.getInstance("AES/CTR/NOPADDING"); ctr.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(n)); return ctr.doFinal(ciphertext, ivSizeInBytes, plaintextLength); }
/** * Decryptes {@code ciphertext} with the following format: {@code nonce || actual_ciphertext || * tag} * * @param ciphertext with format {@code nonce || actual_ciphertext || tag} * @param additionalData additional data * @return plaintext if authentication is successful * @throws GeneralSecurityException when ciphertext is shorter than nonce size + tag size * @throws AEADBadTagException when the tag is invalid */ private byte[] decrypt(ByteBuffer ciphertext, final byte[] additionalData) throws GeneralSecurityException { if (ciphertext.remaining() < snuffle.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES) { throw new GeneralSecurityException("ciphertext too short"); } int firstPosition = ciphertext.position(); byte[] tag = new byte[MAC_TAG_SIZE_IN_BYTES]; ciphertext.position(ciphertext.limit() - MAC_TAG_SIZE_IN_BYTES); ciphertext.get(tag); // rewind to read ciphertext and compute tag. ciphertext.position(firstPosition); ciphertext.limit(ciphertext.limit() - MAC_TAG_SIZE_IN_BYTES); byte[] nonce = new byte[snuffle.nonceSizeInBytes()]; ciphertext.get(nonce); try { Poly1305.verifyMac(getMacKey(nonce), macDataRfc7539(additionalData, ciphertext), tag); } catch (GeneralSecurityException ex) { throw new AEADBadTagException(ex.toString()); } // rewind to decrypt the ciphertext. ciphertext.position(firstPosition); return snuffle.decrypt(ciphertext); }
private static void testModifiedAssociatedData(int keySize) throws GeneralSecurityException { byte[] key = Random.randBytes(keySize); DeterministicAead crypter = new AesSiv(key); byte[] plaintext = Random.randBytes(10); byte[] aad = Random.randBytes(10); byte[] ciphertext = crypter.encryptDeterministically(plaintext, aad); // Flipping bits of aad. for (int b = 0; b < aad.length; b++) { for (int bit = 0; bit < 8; bit++) { byte[] modified = Arrays.copyOf(aad, aad.length); modified[b] ^= (byte) (1 << bit); try { byte[] unused = crypter.decryptDeterministically(ciphertext, modified); fail("Decrypting modified aad should fail"); } catch (AEADBadTagException ex) { // This is expected. } } } }
public static void main(String[] args) throws Exception { newGCMParameterSpecFail(-1, bytes); newGCMParameterSpecFail(128, null); newGCMParameterSpecPass(128, bytes); newGCMParameterSpecFail(-1, bytes, 2, 4); newGCMParameterSpecFail(128, null, 2, 4); newGCMParameterSpecFail(128, bytes, -2, 4); newGCMParameterSpecFail(128, bytes, 2, -4); newGCMParameterSpecFail(128, bytes, 2, 15); // one too many newGCMParameterSpecPass(128, bytes, 2, 14); // ok. newGCMParameterSpecPass(96, bytes, 4, 4); newGCMParameterSpecPass(96, bytes, 0, 0); // Might as well check the Exception constructors. try { new AEADBadTagException(); new AEADBadTagException("Bad Tag Seen"); } catch (Exception e) { e.printStackTrace(); failed++; } if (failed != 0) { throw new Exception("Test(s) failed"); } }
static void gcm_AEADBadTag() throws Exception { Cipher c; byte[] read = new byte[200]; System.out.println("Running gcm_AEADBadTag"); // Encrypt 100 bytes with AES/GCM/PKCS5Padding byte[] ct = encryptedText("GCM", 100); // Corrupt the encrypted message ct = corruptGCM(ct); // Create stream for decryption CipherInputStream in = getStream("GCM", ct); try { int size = in.read(read); throw new RuntimeException("Fail: CipherInputStream.read() " + "returned " + size + " and didn't throw an exception."); } catch (IOException e) { Throwable ec = e.getCause(); if (ec instanceof AEADBadTagException) { System.out.println(" Pass."); } else { System.out.println(" Fail: " + ec.getMessage()); throw new RuntimeException(ec); } } finally { in.close(); } }
@Test public void mapUuidsToKeys_whenTheActiveKeyIsTheOnlyKey_whenThereIsNoMatchingCanaryInTheDatabase_whenDecryptingWithTheWrongKeyRaisesAnInternalException_itShouldCreateACanaryForTheKey() throws Exception { when(encryptionKeysConfiguration.getKeys()).thenReturn(asList(activeKeyData)); EncryptionKeyCanary nonMatchingCanary = new EncryptionKeyCanary(); nonMatchingCanary.setUuid(UUID.randomUUID()); nonMatchingCanary.setEncryptedCanaryValue("fake-non-matching-encrypted-value".getBytes()); nonMatchingCanary.setNonce("fake-non-matching-nonce".getBytes()); when(encryptionKeyCanaryDataService.findAll()) .thenReturn(Arrays.asList(nonMatchingCanary)); when(encryptionService .decrypt(activeKey, nonMatchingCanary.getEncryptedCanaryValue(), nonMatchingCanary.getNonce())) .thenThrow(new AEADBadTagException()); when(encryptionKeyCanaryDataService.save(any(EncryptionKeyCanary.class))) .thenReturn(activeKeyCanary); subject = new EncryptionKeyCanaryMapper(encryptionKeyCanaryDataService, encryptionKeysConfiguration, encryptionService, timedRetry, true); subject.mapUuidsToKeys(keySet); assertCanaryValueWasEncryptedAndSavedToDatabase(); }
@Test public void isMatchingCanary_whenDecryptThrowsAEADBadTagException_returnsFalse() throws Exception { subject = new LunaKeyProxy(encryptionKey, new InternalEncryptionService(new PasswordKeyProxyFactoryTestImpl()) { @Override public String decrypt(Key key, byte[] encryptedValue, byte[] nonce) throws Exception { throw new AEADBadTagException(); } }); assertThat(subject.matchesCanary(mock(EncryptionKeyCanary.class)), equalTo(false)); }
private byte[] gcm(boolean encrypt, byte[] input, byte[] nonce) throws AEADBadTagException { try { Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM); SecretKey secretKey = new SecretKeySpec(key, KEY_ALGORITHM); GCMParameterSpec gcmParameters = new GCMParameterSpec(TAG_BITS, nonce); cipher.init(encrypt ? ENCRYPT_MODE : DECRYPT_MODE, secretKey, gcmParameters); return cipher.doFinal(input); } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) { Throwables.propagateIfInstanceOf(e, AEADBadTagException.class); throw Throwables.propagate(e); } }
@Test(expected = AEADBadTagException.class) public void failsWhenMacIsIncorrect() throws Exception { byte[] ciphertext = encryptor.encrypt(testMessage); int flipIndex = ciphertext.length - 2; byte flipped = (byte)((int)ciphertext[flipIndex] ^ 1); ciphertext[flipIndex] = flipped; encryptor.decrypt(ciphertext); }
@Override public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] associatedData) throws GeneralSecurityException { if (ciphertext.length < AesUtil.BLOCK_SIZE) { throw new GeneralSecurityException("Ciphertext too short."); } Cipher aesCtr = EngineFactory.CIPHER.getInstance("AES/CTR/NoPadding"); byte[] expectedIv = Arrays.copyOfRange(ciphertext, 0, AesUtil.BLOCK_SIZE); byte[] ivForJavaCrypto = expectedIv.clone(); ivForJavaCrypto[8] &= (byte) 0x7F; // 63th bit from the right ivForJavaCrypto[12] &= (byte) 0x7F; // 31st bit from the right aesCtr.init( Cipher.DECRYPT_MODE, new SecretKeySpec(this.aesCtrKey, "AES"), new IvParameterSpec(ivForJavaCrypto)); byte[] ctrCiphertext = Arrays.copyOfRange(ciphertext, AesUtil.BLOCK_SIZE, ciphertext.length); byte[] decryptedPt = aesCtr.doFinal(ctrCiphertext); byte[] computedIv = s2v(associatedData, decryptedPt); if (Bytes.equal(expectedIv, computedIv)) { return decryptedPt; } else { throw new AEADBadTagException("Integrity check failed."); } }
public synchronized byte[] encrypt(byte[] plaintext) throws AEADBadTagException { byte[] nonce = new byte[NONCE_LENGTH]; secureRandom.nextBytes(nonce); return Bytes.concat(nonce, gcm(ENCRYPT, plaintext, nonce)); }
public byte[] decrypt(byte[] ciphertext) throws AEADBadTagException { return gcm(DECRYPT, ciphertextWithoutNonce(ciphertext), getNonce(ciphertext)); }