Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Security

Encryption

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

Concepts in this lab

Encryption
Encryption is calculating text (plaintext) together with a key to turn it into a value that cannot be read (ciphertext). A party that has the same key decrypts that value and gets the original text back, but a party that has no key cannot. Text that is sent or stored without encryption can be read as it is by anyone in the middle. The difference from hashing is that hashing makes it so that no one can turn the value back into the original text, while encryption makes it so that only a party that has the key can turn it back. AES is a symmetric block cipher defined by FIPS 197 that does both encryption and decryption, and in Java you use it through the javax.crypto.Cipher class.
Decryption
Decryption is calculating an encrypted value together with a key to turn it back into the original text. In the symmetric-key method, you must put in the same key that was used to encrypt. A method such as AES-GCM, which attaches a check value (tag) to the value, reports a failure instead of giving out text when the key is different or the value was changed and the tag does not match (FAIL in SP 800-38D), and Java's Cipher throws AEADBadTagException in that case. In a method with no check value, a different key can give wrong bytes with no exception, so you must not trust that the key was right just because the decryption finished.
Key
A key is a value that you put into the encryption and decryption calculation together with the text or the value. AES uses keys that are 128, 192, or 256 bits long. The algorithm is a public standard such as FIPS 197, and whether you can decrypt a value depends on whether you have the key, so what you must protect is the key. In the symmetric-key method, the sender and the receiver have the same key, and sharing that key safely is a separate task. In GCM, you must use a different IV (nonce) each time you encrypt with the same key, and if it is repeated, it becomes open to forgery attacks (SP 800-38D section 8, the Cipher documentation).
Symmetric-key cryptography and public-key cryptography
Symmetric-key cryptography uses the same key to encrypt and to decrypt. It is fast, but it has the problem that the key must be shared safely with the other party (AES). Public-key cryptography uses a pair of keys, a public key and a private key. Data encrypted with the public key can be decrypted only with the private key, so there is no key-sharing problem, but it is slow (RSA). In practice, public-key cryptography is used to exchange a symmetric key safely, and then the symmetric key encrypts the data.

Try these

Running with the default settings

  1. The SecretKey key = newKey(); line runs. The helper method newKey() makes one key and puts it in key. A key is a value that you put into the calculation together with the text, both when you encrypt and when you decrypt.
  2. On the String sent = encrypt("Card number 1234", key); line, the sender puts the text "Card number 1234" and the key into encrypt. encrypt calculates the text together with the key and turns it into a different value.
  3. The calculation ends, and a value that cannot be read comes out and is put in sent. In this lab, a value that starts with ih/DXie50GTx… and is 60 characters long came out. You cannot read "Card number 1234" from this value. If you encrypt the same text again with the same key, a different value comes out, so this value is one example.
  4. The value of sent goes through the middle (a network or storage) to the receiver. This value is all that is visible in the middle. The text and the key were not sent to the middle, so they are not visible.
  5. On the String read = decrypt(sent, key); line, the receiver puts the received value sent and the same key into decrypt. decrypt does decryption, the calculation that reverses encryption.
  6. The result of the decryption is the original text "Card number 1234", and it is put in read. This is because you put in the same key that was used to encrypt.
  7. System.out.println(sent) prints the whole 60-character value that cannot be read, and System.out.println(read) prints Card number 1234.

Result of this run

Code

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);

Assumptions of this model

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.

Previous lab: Hashing · Next lab: Origins

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these