Cryptography Primer : Hashing vs Encoding vs Encryption

Rohit Singh
Javarevisited
Published in
3 min readDec 6, 2023
Hashing vs Encoding vs Encryption

Recently, while working on a project, I had the requirement to create a hash for the JSON string for the idempotency requirement. This leads me to look into Java cryptography APIs in detail, and I will share my experience here.

You will usually come across these terms and sometimes wonder when to use them.

Before I answer that, let’s see the origin.

What is cryptography?

Cryptography literally means “hidden writing” and provides a means to protect information from unauthorised access. Cryptography in Java is based on the Java Cryptography Architecture. Java Cryptography is part of the standard JDK and contains a set of APIs for digital signatures, message digests, certificates, encryption, key management, secure random number generation, etc. The architecture was designed on the principles of interoperability and extensibility. It is built on the concept of provider architecture. BouncyCastle is one of the leading providers of cryptographic APIs. We will not discuss the architecture here but will look at these from a usage point of view, which I think would be very helpful for anyone.

The biggest advantage you will see here is that you do not need any more third-party libraries to implement them. One can implement these using JDK only, as shown below. I have built and tested these on JDK17.

So, what functions are used in cryptography? Let’s start with simple hashing.

Hashing does data transformation by encoding it using a hashing algorithm. In simpler words, using hashing, we create a small, fixed length of data that represents original data, and if any data in the original is changed and another hash is made, two hashes will not match. Since you cannot decode the hashed data, it is a perfect candidate for creating checksums to validate data integrity. Examples of hashing algorithms are SHA-256, SHA-3–256, and SHA-512.

Let’s go into usage to understand how it will help you:

How can we hash data? We will hash data using the SHA-256 (Secure Hashing) algorithm. Below is the code sample.

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
String input = "This is test";
byte[] hash = messageDigest.digest(input.getBytes(StandardCharsets.UTF_8));
System.out.println(HexFormat.of().withUpperCase().formatHex(hash));

Output : 5C00DD7BD7B2F948B7867F987BF194487CC0DD7963E1D71E0CD1FCD956324BAF

Use Cases:

  • Idempotency Checks in a Distributed System.
  • Verify the user’s password.
  • Fraud checks

Advanced hashing:

  • Keyed-hashing (HMAC)

Next, what is encoding? How do we encode data?

Encoding means transforming the data into a format that can be decoded by others using a publicly available algorithm. The purpose here is to maintain data integrity. Examples of encoding algorithms are: ASCII, Hex (Base16), Base64, etc.

Let’s take an example, and here we will encode and decode using Base64.

import java.nio.charset.StandardCharsets;
import java.util.Base64;

String input = "This is test";
String encoded = Base64.getEncoder().withoutPadding().encodeToString(input.getBytes(StandardCharsets.UTF_8));
String decoded = new String(Base64.getDecoder().decode(encoded));
System.out.println(input.equals(decoded) ? "Pass" : "Fail");

Use Cases:

  • Data transfer between two systems when it cannot be transmitted in its current format or between incompatible systems.

And finally, how can we encrypt data?

Encryption is data transformation where data is also encoded using an encryption algorithm but with a secret key. So, if you know the key, you can decrypt the data. The purpose here is to keep the data secure from unauthorised access. Examples of encryption algorithms are : AES, RSA, etc.

In our use case, we will use a secret key to encrypt the data using the AES encryption algorithm.

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

// generate secret key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
SecretKey aesKey = keygen.generateKey();

// encrypt
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] input = "This is test".getBytes(StandardCharsets.UTF_8);
byte[] encryptedInput = aesCipher.doFinal(input);

//decrypt
aesCipher.init(Cipher.DECRYPT_MODE, aesKey);
byte[] decryptedInput = aesCipher.doFinal(encryptedInput);

System.out.println(Arrays.equals(decryptedInput, input) ? "Pass" : "Fail");

Use Cases:

  • Managing digital client and server certificates (pfx files) using PKI.
  • Peer to peer secure messaging.
  • Sensitive information exchange between parties.

And lastly….

JCA APIs are vast, and I have only scratched the surface as the focus is on day to day development usage, depending on the use case. I hope this will help everyone learn these concepts and can be used wherever you see the need to do that.

--

--

Rohit Singh
Javarevisited

Passionate technology professional, enthusiastic developer, and a devoured reader