package at.favre.lib.bytes.otherPackage; | |
import org.junit.Test; | |
import javax.crypto.Cipher; | |
import javax.crypto.SecretKey; | |
import javax.crypto.spec.GCMParameterSpec; | |
import javax.crypto.spec.SecretKeySpec; | |
import java.nio.ByteBuffer; | |
import java.nio.charset.StandardCharsets; | |
import java.security.SecureRandom; | |
import java.security.spec.AlgorithmParameterSpec; | |
import static org.junit.Assert.assertEquals; | |
/** | |
* A simple showcase for encryption and decryption with AES + GCM in Java | |
*/ | |
public class AesGcmTest { | |
private final SecureRandom secureRandom = new SecureRandom(); | |
private final static int GCM_IV_LENGTH = 12; | |
@Test | |
public void testEncryption() throws Exception { | |
//create new random key | |
byte[] key = new byte[16]; | |
secureRandom.nextBytes(key); | |
SecretKey secretKey = new SecretKeySpec(key, "AES"); | |
byte[] associatedData = "ProtocolVersion1".getBytes(StandardCharsets.UTF_8); //meta data you want to verify with the secret message | |
String message = "the secret message"; | |
byte[] cipherText = encrypt(message, secretKey, associatedData); | |
String decrypted = decrypt(cipherText, secretKey, associatedData); | |
assertEquals(message, decrypted); | |
} | |
/** | |
* Encrypt a plaintext with given key. | |
* | |
* @param plaintext to encrypt (utf-8 encoding will be used) | |
* @param secretKey to encrypt, must be AES type, see {@link SecretKeySpec} | |
* @param associatedData optional, additional (public) data to verify on decryption with GCM auth tag | |
* @return encrypted message | |
* @throws Exception if anything goes wrong | |
*/ | |
private byte[] encrypt(String plaintext, SecretKey secretKey, byte[] associatedData) throws Exception { | |
byte[] iv = new byte[GCM_IV_LENGTH]; //NEVER REUSE THIS IV WITH SAME KEY | |
secureRandom.nextBytes(iv); | |
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); | |
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length | |
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); | |
if (associatedData != null) { | |
cipher.updateAAD(associatedData); | |
} | |
byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); | |
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length); | |
byteBuffer.put(iv); | |
byteBuffer.put(cipherText); | |
return byteBuffer.array(); | |
} | |
/** | |
* Decrypts encrypted message (see {@link #encrypt(String, SecretKey, byte[])}). | |
* | |
* @param cipherMessage iv with ciphertext | |
* @param secretKey used to decrypt | |
* @param associatedData optional, additional (public) data to verify on decryption with GCM auth tag | |
* @return original plaintext | |
* @throws Exception if anything goes wrong | |
*/ | |
private String decrypt(byte[] cipherMessage, SecretKey secretKey, byte[] associatedData) throws Exception { | |
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); | |
//use first 12 bytes for iv | |
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, GCM_IV_LENGTH); | |
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv); | |
if (associatedData != null) { | |
cipher.updateAAD(associatedData); | |
} | |
//use everything from 12 bytes on as ciphertext | |
byte[] plainText = cipher.doFinal(cipherMessage, GCM_IV_LENGTH, cipherMessage.length - GCM_IV_LENGTH); | |
return new String(plainText, StandardCharsets.UTF_8); | |
} | |
} |
It is not needed, it's a JUnit test.
To see where to start look at @Test
annotated method. It's being run by the test runner every time the test phase of the project runs.
@cezary-butler Thanks for sharing this piece of code i was struggling for 24H to debug Tag mismatch exception and your approach worked
@Amine-CHICHI thanks for the credit but I am not the one who should receive it. The code was shared by @patrickfav
Hi, if I want to separate encrypt and decrypt on different class, how can I call the decryptor method? (byte param, etc)?
is there any way to get authentication tag ?
The tag is automatically appended to the cipher text (should be the last 16 byte of cipherText
).
@patrickfav, in your opinion, is it safe to use this code in a Production environment to encrypt API keys in a database?
If we assume for sure that the SecretKey (in base64) is kept securely outside of the database.
@sisimomo yes, as long as you keep the secret key confidential; however if you are unsure about this I would recommend using a library with a higher abstraction, that manages these things for you. But whouldnt it suffice to hash the api keys with e.g. bcrypt?
Sorry if that wasn't clear, but in my case hashing the API Key won't do the trick as I understand it. I need to save the API key in the database so that I can call an external service using the user's API key. So in my case I need to be able to retrieve the original supplied API key to send to the external API service.
I agree, without your well-documented example code to base my code on, I definitely would have used a library with higher abstraction. But I like to use as few external libraries as possible, especially if I can get a similar result with native Java.
Thanks for your example. It saved me a day or 2 of debugging and development. :)
Where is the main file for this class file?