@Override public InputStream newCipherInputStream(InputStream underlyingInputStream, byte[] secretKey, byte[] iv) throws CipherException { AEADBlockCipher cipher = new GCMBlockCipher(new TwofishEngine()); cipher.init(false, new AEADParameters(new KeyParameter(secretKey), MAC_SIZE, iv)); return new org.bouncycastle.crypto.io.CipherInputStream(underlyingInputStream, cipher); } }
@Override public OutputStream newCipherOutputStream(OutputStream underlyingOutputStream, byte[] secretKey, byte[] iv) throws CipherException { AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init(true, new AEADParameters(new KeyParameter(secretKey), MAC_SIZE, iv)); return new org.bouncycastle.crypto.io.CipherOutputStream(underlyingOutputStream, cipher); }
@Override public OutputStream newCipherOutputStream(OutputStream underlyingOutputStream, byte[] secretKey, byte[] iv) throws CipherException { AEADBlockCipher cipher = new GCMBlockCipher(new TwofishEngine()); cipher.init(true, new AEADParameters(new KeyParameter(secretKey), MAC_SIZE, iv)); return new org.bouncycastle.crypto.io.CipherOutputStream(underlyingOutputStream, cipher); }
@Override public InputStream newCipherInputStream(InputStream underlyingInputStream, byte[] secretKey, byte[] iv) throws CipherException { AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init(false, new AEADParameters(new KeyParameter(secretKey), MAC_SIZE, iv)); return new org.bouncycastle.crypto.io.CipherInputStream(underlyingInputStream, cipher); } }
@Test public void testE_BouncyCastleCipherInputStreamWithAesGcmLongPlaintext() throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { // Encrypt (not interesting in this example) byte[] randomKey = createRandomArray(16); byte[] randomIv = createRandomArray(16); byte[] originalPlaintext = createRandomArray(4080); // <<<< 4080 bytes fails, 4079 bytes works! byte[] originalCiphertext = encryptWithAesGcm(originalPlaintext, randomKey, randomIv); // Decrypt with BouncyCastle implementation of CipherInputStream AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init(false, new AEADParameters(new KeyParameter(randomKey), 128, randomIv)); try { readFromStream(new org.bouncycastle.crypto.io.CipherInputStream(new ByteArrayInputStream(originalCiphertext), cipher)); // ^^^^^^^^^^^^^^^ INTERESTING PART ^^^^^^^^^^^^^^^^ // // In this example, the BouncyCastle implementation of the CipherInputStream throws an ArrayIndexOutOfBoundsException. // The only difference to the example above is that the plaintext is now 4080 bytes long! For 4079 bytes plaintexts, // everything works just fine. System.out.println("Test E: org.bouncycastle.crypto.io.CipherInputStream: OK, throws no exception"); } catch (IOException e) { fail("Test E: org.bouncycastle.crypto.io.CipherInputStream: NOT OK throws: "+e.getMessage()); } }
cipher.init(false, new AEADParameters(new KeyParameter(randomKey), 128, randomIv));
public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { cipher.init(forEncryption, params); }
@Override public void init(final boolean forEncryption, final CipherParameters params) throws CryptoException { try { cipherDelegate.init(forEncryption, params); } catch (RuntimeException e) { throw new CryptoException("Cipher initialization error", e); } }
@Override protected void _udpEncrypt(byte[] data, ByteArrayOutputStream stream) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(data); int remaining = buffer.remaining(); buffer.get(encBuffer, 0, remaining); encCipher.init(true, getCipherParameters(true)); encCipher.doFinal( encBuffer, encCipher.processBytes(encBuffer, 0, remaining, encBuffer, 0) ); stream.write(encBuffer, 0, remaining + getTagLength()); }
@Override protected void _udpDecrypt(byte[] data, ByteArrayOutputStream stream) throws Exception { ByteBuffer buffer = ByteBuffer.wrap(data); int remaining = buffer.remaining(); buffer.get(decBuffer, 0, remaining); decCipher.init(false, getCipherParameters(false)); decCipher.doFinal( decBuffer, decCipher.processBytes(decBuffer, 0, remaining, decBuffer, 0) ); stream.write(decBuffer, 0, remaining - getTagLength()); } }
/** Create an encrypting, authenticating OutputStream. Will write the nonce to the stream. * @param os The underlying OutputStream. * @param key The encryption key. * @param nonce The nonce. This serves the function of an IV. As a nonce, this MUST be unique. * We will write it to the stream so the other side can pick it up, like an IV. Should * generally be generated from a SecureRandom. The top bit must be 0, i.e. nonce[0] &= 0x7F. * @param mainCipher The BlockCipher for encrypting data. E.g. AES; not a block mode. This will * be used for encrypting a fairly large amount of data so could be any of the 3 BC AES impl's. * @param hashCipher The BlockCipher for the final hash. E.g. AES, not a block mode. This will * not be used very much so should be e.g. an AESLightEngine. */ public AEADOutputStream(OutputStream os, byte[] key, byte[] nonce, BlockCipher hashCipher, BlockCipher mainCipher) throws IOException { super(os); os.write(nonce); cipher = new OCBBlockCipher_v149(hashCipher, mainCipher); KeyParameter keyParam = new KeyParameter(key); AEADParameters params = new AEADParameters(keyParam, MAC_SIZE_BITS, nonce); cipher.init(true, params); }
/** Create a decrypting, authenticating InputStream. IMPORTANT: We only authenticate when * closing the stream, so do NOT use Closer.close() etc and swallow IOException's on close(), * as that's what we will throw if authentication fails. We will read the nonce from the * stream; it functions similarly to an IV. * @param is The underlying InputStream. * @param key The encryption key. * @param nonce The nonce. This serves the function of an IV. As a nonce, this MUST be unique. * We will write it to the stream so the other side can pick it up, like an IV. * @param mainCipher The BlockCipher for encrypting data. E.g. AES; not a block mode. This will * be used for encrypting a fairly large amount of data so could be any of the 3 BC AES impl's. * @param hashCipher The BlockCipher for the final hash. E.g. AES, not a block mode. This will * not be used very much so should be e.g. an AESLightEngine. */ public AEADInputStream(InputStream is, byte[] key, BlockCipher hashCipher, BlockCipher mainCipher) throws IOException { super(is); byte[] nonce = new byte[mainCipher.getBlockSize()]; new DataInputStream(is).readFully(nonce); cipher = new OCBBlockCipher_v149(hashCipher, mainCipher); KeyParameter keyParam = new KeyParameter(key); AEADParameters params = new AEADParameters(keyParam, MAC_SIZE_BITS, nonce); cipher.init(false, params); excess = new byte[mainCipher.getBlockSize()]; excessEnd = 0; excessPtr = 0; }
@Override protected AEADBlockCipherAdapter newCipher(final CiphertextHeader header, final boolean mode) { final AEADBlockCipher cipher = blockCipherSpec.newInstance(); final SecretKey key = lookupKey(header.getKeyName()); final AEADParameters params = new AEADParameters( new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, header.getNonce(), header.encode()); cipher.init(mode, params); return new AEADBlockCipherAdapter(cipher); } }
@Override public byte[] encrypt(byte[] data, byte[] randomKeyBytes) throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidCipherTextException { AEADBlockCipher cipher = new GCMBlockCipher(new AESFastEngine()); cipher.init(true, new AEADParameters(new KeyParameter(randomKeyBytes), 128, randomIvBytes)); return cipherData(cipher, data); }
/** * Given the IV, encrypt the provided data * * @param IV initialization vector * @param message data to be encrypted * @return byte array with the cipher text * @throws RuntimeException */ public byte[] encrypt(final byte[] IV, final byte[] message) throws RuntimeException { AEADParameters aeadParams = new AEADParameters( new KeyParameter(key), TAG_LENGTH, IV, authData); cipher.init(true, aeadParams); byte[] cipherText = newBuffer(cipher.getOutputSize(message.length)); int outputOffset = cipher.processBytes(message, 0, message.length, cipherText, 0); try { cipher.doFinal(cipherText, outputOffset); } catch (InvalidCipherTextException e) { throw new RuntimeException("Error: ", e); } return cipherText; }
@Override public byte[] decrypt(byte[] data, byte[] randomKey) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalStateException, InvalidCipherTextException { AEADBlockCipher cipher = new GCMBlockCipher(new AESFastEngine()); cipher.init(false, new AEADParameters(new KeyParameter(randomKey), 128, randomIvBytes)); return cipherData(cipher, data); }
/** * Encrypts data using an AEAD cipher. A {@link CiphertextHeader} is prepended to the resulting ciphertext and used as * AAD (Additional Authenticated Data) passed to the AEAD cipher. * * @param cipher AEAD cipher. * @param key Encryption key. * @param nonce Nonce generator. * @param data Plaintext data to be encrypted. * * @return Concatenation of encoded {@link CiphertextHeader} and encrypted data that completely fills the returned * byte array. * * @throws CryptoException on encryption errors. */ public static byte[] encrypt(final AEADBlockCipher cipher, final SecretKey key, final Nonce nonce, final byte[] data) throws CryptoException { final byte[] iv = nonce.generate(); final byte[] header = new CiphertextHeader(iv).encode(); cipher.init(true, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, iv, header)); return encrypt(new AEADBlockCipherAdapter(cipher), header, data); }
/** * Given the IV, decrypt the provided data * * @param IV initialization vector * @param cipherText data to be decrypted * @return byte array with the plain text * @throws RuntimeException */ public byte[] decrypt(byte[] IV, byte[] cipherText) throws RuntimeException { AEADParameters aeadParams = new AEADParameters( new KeyParameter(key), TAG_LENGTH, IV, authData); cipher.init(false, aeadParams); byte[] buffer = newByteArray(cipherText); byte[] plainText = newBuffer(cipher.getOutputSize(cipherText.length)); int outputOffset = cipher.processBytes(buffer, 0, buffer.length, plainText, 0); try { cipher.doFinal(plainText, outputOffset); } catch (InvalidCipherTextException e) { throw new RuntimeException("Error: ", e); } return plainText; }
/** * Decrypts data using an AEAD cipher. * * @param cipher AEAD cipher. * @param key Encryption key. * @param data Ciphertext data containing a prepended {@link CiphertextHeader}. The header is treated as AAD input * to the cipher that is verified during decryption. * * @return Decrypted data that completely fills the returned byte array. * * @throws CryptoException on encryption errors. * @throws EncodingException on decoding cyphertext header. */ public static byte[] decrypt(final AEADBlockCipher cipher, final SecretKey key, final byte[] data) throws CryptoException, EncodingException { final CiphertextHeader header = CiphertextHeader.decode(data); final byte[] nonce = header.getNonce(); final byte[] hbytes = header.encode(); cipher.init(false, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, nonce, hbytes)); return decrypt(new AEADBlockCipherAdapter(cipher), data, header.getLength()); }
/** * Decrypts data using an AEAD cipher. * * @param cipher AEAD cipher. * @param key Encryption key. * @param input Input stream containing a {@link CiphertextHeader} followed by ciphertext data. The header is * treated as AAD input to the cipher that is verified during decryption. * @param output Output stream that receives plaintext produced by block cipher in decryption mode. * * @throws CryptoException on encryption errors. * @throws EncodingException on decoding cyphertext header. * @throws StreamException on IO errors. */ public static void decrypt( final AEADBlockCipher cipher, final SecretKey key, final InputStream input, final OutputStream output) throws CryptoException, EncodingException, StreamException { final CiphertextHeader header = CiphertextHeader.decode(input); final byte[] nonce = header.getNonce(); final byte[] hbytes = header.encode(); cipher.init(false, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, nonce, hbytes)); process(new AEADBlockCipherAdapter(cipher), input, output); }