The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Security
What is encryption, and what happens without the key?
Encryption means calculating text together with a key to turn it into a value that cannot be read. In code, you pass the text and a key to encrypt("Card number 1234", key), and you get back a value that cannot be read. A party that has the same key passes that value and the key to decrypt(sent, key) and gets the original text back. This is called decryption. A party that has no key, or a different key, cannot get the text back, so what you must protect is the key, not the value. In this lab, you change the key that the receiver has to the same key, a different key, or no key, and sometimes you remove the encryption itself. You see the value that is visible in the middle and what the receiver read, both checked by actually running the code. (The bodies of newKey, which makes a key, and of encrypt and decrypt, which do the calculation, are shortened on the screen.)
Checked against real execution · Compiled and ran all 6 combinations on JDK 21 with javax.tools and checked against the results, and checked the calculation with Node.js and the NIST test vectors
SecretKey key = newKey(); // Create one key
// Sender: encrypt the text with the key
String sent = encrypt("Card number 1234", key);
// Receiver: decrypt with the same key
String read = decrypt(sent, key);
System.out.println(sent);
System.out.println(read);
We did the actual calculation with javax.crypto.Cipher("AES/GCM/NoPadding") of Java JDK 21.0.12.1. The key is 256 bits, the tag is 128 bits, and a new 12-byte nonce is made for each encryption and put in front of the result, which is written in Base64. What is shortened on the screen is the import lines, the declaration of main, and the bodies of the helper methods newKey, encrypt, and decrypt. We checked all 6 combinations against the result of this: we put the same lines as the code on the screen into main, compiled that source with javax.tools, loaded the class with a new class loader, ran Main.main, and caught the standard output and the thrown exception. What we checked against is the value visible in the middle (the first 12 characters and the total number of characters of the value that encrypt returned), what the receiver read, whether the original text was recovered, the name of the exception thrown when decrypting with a different key (javax.crypto.AEADBadTagException, with the message Tag mismatch), and that nothing is printed at that time. The encrypted value is different every run. To compare, we fixed the key and the nonce to set values so that the same value comes out (in real use both are made at random), and ih/DXie50GTx…(60 characters) on the screen is one example obtained that way. When we encrypted the same text with the same key twice, making a new nonce each time, the two values were different from each other and both could be decrypted, and when we ran the 6 combinations again with a randomly made key and nonce, the kind of result (the text that was read, the name of the exception, the length of the value) was the same. The first 12 characters shown on the screen are the nonce part put in front of the value (the first 9 bytes), and the encrypted text part follows after it. The length of the value changes with the length of the text. We checked in two ways that the calculation follows the standard. We encrypted the same key, nonce, and text with aes-256-gcm of Node.js too, and the ciphertext and the tag were the same as Java down to the byte, and Node.js decrypted the Java value with the same key but could not with a different key. Also, in the GCM test vectors that the NIST Cryptographic Algorithm Validation Program (CAVP) publishes (gcmEncryptExtIV256.rsp and gcmDecrypt256.rsp in gcmtestvectors.zip; key 256 bits, IV 96 bits, text 128 bits, no additional data, tag 128 bits), the ciphertext and tag of one encryption case and the results of two decryption cases (one that passes and one that is FAIL) were the same as the Java calculation. We opened the documents ourselves and checked them. NIST FIPS 197 (the 2023 update), item 3 of the preface, introduces AES as a symmetric block cipher that can encrypt and decrypt, and says the key length is 128, 192, or 256 bits and the data is in 128-bit blocks. NIST SP 800-38D (the 2007 edition) says in section 3 that GCM guarantees the confidentiality and the authentication of data together, defines in sections 5.2.2 and 7.2 that authenticated decryption outputs the text if the check value (tag) matches and FAIL instead of the text if it does not, requires in section 8 that the probability of encrypting different data with the same key and the same IV be 2 to the power of -32 or less, and recommends in section 5.2.1.1 that the IV length be 96 bits. The Java 21 Cipher documentation explains that in AEAD modes (GCM and others) the tag is put after the ciphertext and is checked when decrypting, and tells you to change the IV every time you encrypt with the same key, and the AEADBadTagException documentation explains that it is thrown when a Cipher in an AEAD mode cannot verify the tag. This lab covers only the method in which the sender and the receiver use the same key (symmetric-key encryption). The method with two keys (public key) and signatures are covered in the in-depth lab "Hashing, encryption, and signatures", and how the receiver gets the same key safely (key delivery) is not covered. An exception is thrown when decrypting with a different key because the method this lab chose (AES-GCM) checks with the tag whether the value was changed or the key is different. A method without such a check can behave differently, and when we decrypted with a different key using AES/CTR/NoPadding, bytes other than the original text came out with no exception. How to store the key safely is not covered.
Struck-through text was removed from the first version of the code; highlighted text was added.