Source
Hashing
SHA-256
AES Mode
/**
* Hash algorithm. It's recommended to use SHA-256 over legacy (non-trusted) algorithms such as SHA-1 or MD5
*/
public static final String DEFAULT_HASH_ALGORITHM = "SHA-256"; // ["SHA-256", "SHA-1", "MD5"]
AES Encrypt
private HashUtils.HashData hash(final byte[] data) throws NoSuchAlgorithmException {
// Create Hash function to generate the digest using the specified algorithm.
final var messageDigest = MessageDigest.getInstance(DEFAULT_HASH_ALGORITHM);
final var hash = messageDigest.digest(data); // (1)!
return new HashUtils.HashData(data, hash, EncodeUtils.encode(hash));
}
private HashUtils.HashData hashWithSalt(final byte[] data, final byte[] salt) throws NoSuchAlgorithmException {
// Create Hash function to generate the digest using the specified algorithm.
final var messageDigest = MessageDigest.getInstance(DEFAULT_HASH_ALGORITHM);
messageDigest.update(salt); // (2)!
final var hash = messageDigest.digest(data); // (3)!
return new HashUtils.HashData(data, hash, EncodeUtils.encode(hash));
}
- Compute the Hash directly using
SHA-256
algorithm. - Apply a Salt to data before computing the Hash.
- Compute the Hash using
SHA-256
algorithm.
PBKDF2
AES Mode
/**
* SHA-256 is not enough for password hashing even using salt that is the fundamental for password hashing.
* PBKDF2 applies a pseudorandom function, such as hash-based message authentication code (HMAC), to the
* input password or passphrase along with a salt value and repeats the process many times to produce a
* derived key, which can then be used as a cryptographic key in subsequent operations. The added computational
* work makes password cracking much more difficult, and is known as key stretching.
* PBKDF2, BCrypt, and SCrypt. BCrypt, and SCrypt does not ship with Java SDK by default
*/
public static final String HASH_ALGORITHM = "PBKDF2WithHmacSHA1";
public static final int ITERATION_COUNT = 65536;
public static final int KEY_LENGTH = 128;
AES Encrypt
private HashUtils.HashData hash(final String password, final byte[] salt) throws GeneralSecurityException {
final var spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
final var factory = SecretKeyFactory.getInstance(HASH_ALGORITHM);
byte[] hash = factory.generateSecret(spec).getEncoded();
return new HashUtils.HashData(password.getBytes(), hash, EncodeUtils.encode(hash));
}
HMAC
AES Mode
/**
* HMAC is a cryptographic method that guarantees the integrity of the message between two parties.
* HMAC algorithm consists of a secret key and a hash function (SHA-256). The secret key is a unique piece
* of information or a string of characters. It is known both by the sender and the receiver of the message.
* The hash function is a mapping algorithm that converts one sequence to another sequence.
*/
public static final String HASH_ALGORITHM = "HmacSHA256";
AES Encrypt
private HashUtils.HashData hmac(final byte[] data, final byte[] key) throws GeneralSecurityException {
final var spec = new SecretKeySpec(key, HASH_ALGORITHM);
final var mac = Mac.getInstance(HASH_ALGORITHM);
mac.init(spec);
final var hmac = mac.doFinal(data);
return new HashUtils.HashData(data, hmac, EncodeUtils.encode(hmac));
}
Symmetric Key
Symmetric Key
/**
* The Advanced Encryption Standard (AES, Rijndael) is a block cipher encryption and decryption
* algorithm, the most used encryption algorithm in the worldwide. The AES processes block of 128
* bits using a secret key of 128, 192, or 256 bits.
*/
public static final String AES = "AES";
/**
* For AES, the legal key sizes are 128, 192, and 256 bits.
*/
public static final int AES_KEY_SIZE = 128;
/**
* The IV (initial value or initialization vector), it is random bytes, typically 12 bytes or 16 bytes
*/
public static final int INITIALIZATION_VECTOR_SIZE = 16;
/**
* @return
* @throws GeneralSecurityException
*/
public static SecretKey generateSymmetricKey() throws GeneralSecurityException {
// Get the Key Generator
final var keyGenerator = KeyGenerator.getInstance(AES);
// Init the AES Symmetric key using 128 bits
keyGenerator.init(AES_KEY_SIZE, SecureRandom.getInstanceStrong());
return keyGenerator.generateKey();
}
/**
* A cryptographic nonce is a randomly generated number designed to keep communications private and
* protect against replay attacks.
*
* @return
*/
public static byte[] getRandomNonce() {
final var nonce = new byte[INITIALIZATION_VECTOR_SIZE];
new SecureRandom().nextBytes(nonce);
return nonce;
}
AES/EBC Mode
AES Mode
/**
* AES + Electronic Code Book (ECB) + NoPadding
* ECB is the easiest block cipher way of functioning, since each block of input plaintext is directly encrypted,
* and the output is in the form of encrypted ciphertext blocks. In general, if a message is bigger than b bits
* in size, it can be divided into many blocks and the process repeated.
*/
public static final String ENCRYPT_ALGORITHM = "AES/ECB/NoPadding";
AES Encrypt
private byte[] encrypt(final byte[] data, final SecretKey secretkey)
throws GeneralSecurityException {
final var encryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secretkey);
return encryptionCipher.doFinal(data);
}
AES Decrypt
private byte[] decrypt(final byte[] data, final SecretKey secretkey)
throws GeneralSecurityException {
final var decryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
decryptionCipher.init(Cipher.DECRYPT_MODE, secretkey);
return decryptionCipher.doFinal(data);
}
AES/CBC Mode
AES Mode
/**
* AES + Cipher Block Chaining (CBC) + NoPadding
* CBC uses the result from the previous encrypted block (XOR) to encrypt the next block (chain).
* The first encrypted block uses an initialization vector (iv) for the XOR operations with random data.
* CBC improves the ECB mode for minimizing patterns in plaintext.
*/
public static final String ENCRYPT_ALGORITHM = "AES/CBC/NoPadding";
AES Encrypt
private byte[] encrypt(final byte[] data, final SecretKey secretkey, final byte[] iv)
throws GeneralSecurityException {
final var encryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
final var ivParameterSpec = new IvParameterSpec(iv);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secretkey, ivParameterSpec);
return encryptionCipher.doFinal(data);
}
AES Decrypt
private byte[] decrypt(final byte[] data, final SecretKey secretkey, final byte[] iv)
throws GeneralSecurityException {
final var decryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
final var ivParameterSpec = new IvParameterSpec(iv);
decryptionCipher.init(Cipher.DECRYPT_MODE, secretkey, ivParameterSpec);
return decryptionCipher.doFinal(data);
}
AES/GCM Mode
AES Mode
/**
* AES + Galois Counter Mode (GCM) + NoPadding
* GCM = CTR + Authentication
* GCM Concatenates an initialization vector with a counter in order to add more randomness to the
* generated encrypted data. This is to avoid repetition and add more noise into the blocks.
* It also adds authentication to the algorithm by the generation of a Tag.
* Don’t use AES Electronic codebook (ECB) Mode. The AES ECB mode, or AES/ECB/PKCS5Padding (in Java)
* is not semantically secure – The ECB-encrypted ciphertext can leak information about the plaintext.
* GCM since it's more secured it takes more time to be computed than other algorithm such as CBC or ECB.
*/
public static final String ENCRYPT_ALGORITHM = "AES/GCM/NoPadding"; // AES/GCM/PKCS5Padding
/**
* GCM is defined for the tag sizes 128, 120, 112, 104, or 96, 64 and 32.
* Note that the security of GCM is strongly dependent on the tag size.
* You should try and use a tag size of 64 bits at the very minimum, but in general a tag
* size of the full 128 bits should be preferred.
*/
public static final int AUTHENTICATION_TAG_SIZE = 128;
AES Encrypt
private byte[] encrypt(final byte[] data, final SecretKey secretkey, final byte[] iv)
throws GeneralSecurityException {
final var encryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secretkey, new GCMParameterSpec(AUTHENTICATION_TAG_SIZE, iv));
return encryptionCipher.doFinal(data);
}
AES Decrypt
private byte[] decrypt(final byte[] data, final SecretKey secretkey, final byte[] iv)
throws GeneralSecurityException {
final var decryptionCipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
decryptionCipher.init(Cipher.DECRYPT_MODE, secretkey, new GCMParameterSpec(AUTHENTICATION_TAG_SIZE, iv));
return decryptionCipher.doFinal(data);
}
Asymmetric
Encryption/Decryption
Generate Key Pair
Asymmetric Key Pair
/**
* In RSA cryptography, both the public and the private keys can encrypt a message. The opposite key
* from the one used to encrypt a message is used to decrypt it. This attribute is one reason why RSA
* has become the most widely used asymmetric algorithm: It provides a method to assure the confidentiality,
* integrity, authenticity, and non-repudiation of electronic communications and data storage.
* RSA requires more intensive processing than AES, because of that RSA is used to encrypt AES keys or
* small data in transit.
*/
public static final String RSA = "RSA";
/**
* For RSA the larger the key the more secure the encryption will be.
*/
public static final int RSA_KEY_SIZE = 2048;
/**
* @return
* @throws Exception
*/
public static KeyPair generateAsymmetricKeyPair() throws NoSuchAlgorithmException {
final var keyPairGenerator = KeyPairGenerator.getInstance(RSA); // (1)!
keyPairGenerator.initialize(RSA_KEY_SIZE); // (2)!
return keyPairGenerator.generateKeyPair();
}
- Get the Key Generator
RSA
in Java crypto service provider (by default isSunJCE
). - Initialize the Key Generator with the requested key size.
Default Mode
RSA Mode
/**
* The Java algorithm string "RSA/ECB/PKCS1Padding", as you already found out, does not implement ECB;
* it only encrypts/decrypts a single block. The Bouncy Castle cryptographic security provider has a better
* named algorithm string, "RSA/None/PKCS1Padding", which better indicates that no mode of operation is used.
* It is likely that "/ECB" was just included to mimic the cipher string for block ciphers. So you would have
* to call the cipher "RSA/ECB/PKCS1Padding" multiple times to implement ECB.
* "PKCS1Padding" indicates RSA with PKCS#1 v1.5 padding for encryption. This padding is indeterministic -
* i.e. it uses a random number generator. This explains why each ciphertext block will be different.
*/
public static final String ENCRYPT_ALGORITHM = "RSA/ECB/PKCS1Padding";
RSA Encrypt
private static byte[] encrypt(final byte[] data, final byte[] publicKey) throws GeneralSecurityException {
final var keyFactory = KeyFactory.getInstance(RSA);
final var publicKeySpec = new X509EncodedKeySpec(publicKey);
final var publicKeyCipher = keyFactory.generatePublic(publicKeySpec);
final var cipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKeyCipher);
return cipher.doFinal(data);
}
RSA Decrypt
private static byte[] decrypt(final byte[] data, final byte[] privateKey) throws GeneralSecurityException {
final var keyFactory = KeyFactory.getInstance(RSA);
final var privateKeySpec = new PKCS8EncodedKeySpec(privateKey);
final var privateKeyCipher = keyFactory.generatePrivate(privateKeySpec);
final var cipher = Cipher.getInstance(ENCRYPT_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKeyCipher);
return cipher.doFinal(data);
}
OAP Mode
RSA Mode
/**
* Optimal Asymmetric Encryption Padding (OAEP) allows for a message to be encrypted using RSA. It thus uses RSA
* encryption and integrates a padding scheme.
* OAP adds an element of randomness which can be used to convert a deterministic encryption scheme (e.g., traditional RSA)
* into a probabilistic scheme.
* OAP prevents partial decryption of ciphertexts (or other information leakage) by ensuring that an adversary cannot recover
* any portion of the plaintext without being able to invert the trapdoor one-way permutation.
*/
public static final String ENCRYPT_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
/**
* Default Provider (It's not needed unless you change the default)
*/
private static final String SUN_JCE = "SunJCE";
/**
* A mask generation function (MGF) is a cryptographic primitive similar to a cryptographic hash function
* except that while a hash function's output has a fixed size, a MGF supports output of a variable length.
* In this respect, a MGF can be viewed as a extendable-output function (XOF): it can accept input of any
* length and process it to produce output of any length. Mask generation functions are completely deterministic:
* for any given input and any desired output length the output is always the same.
*/
private static final String MGF_1 = "MGF1";
private static final String SHA_256 = "SHA-256";
RSA Encrypt
private static byte[] encrypt(final byte[] data, final byte[] publicKey) throws GeneralSecurityException {
final var keyFactory = KeyFactory.getInstance(RSA);
final var publicKeySpec = new X509EncodedKeySpec(publicKey);
final var publicKeyCipher = keyFactory.generatePublic(publicKeySpec);
// Specify the provider to use, since it's not necessary using default provider
final var cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, SUN_JCE);
// It's needed to specify OAEPParameterSpec, since by default it uses SHA-1 instead SHA-256 even it's specified
final var spec = new OAEPParameterSpec(SHA_256, MGF_1, MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.ENCRYPT_MODE, publicKeyCipher, spec);
return cipher.doFinal(data);
}
RSA Decrypt
private static byte[] decrypt(final byte[] data, final byte[] privateKey) throws GeneralSecurityException {
final var keyFactory = KeyFactory.getInstance(RSA);
final var privateKeySpec = new PKCS8EncodedKeySpec(privateKey);
final var privateKeyCipher = keyFactory.generatePrivate(privateKeySpec);
// It's needed to specify OAEPParameterSpec, since by default it uses SHA-1 instead SHA-256 even it's specified
final var cipher = Cipher.getInstance(ENCRYPT_ALGORITHM, SUN_JCE);
final var spec = new OAEPParameterSpec(SHA_256, MGF_1, MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
cipher.init(Cipher.DECRYPT_MODE, privateKeyCipher, spec);
return cipher.doFinal(data);
}
Digital Signature
RSA Sign
private static byte[] sign(final byte[] data, final byte[] privateKey) throws GeneralSecurityException {
// Get the private key encoded
final var keyFactory = KeyFactory.getInstance(RSA);
final var privateKeySpec = new PKCS8EncodedKeySpec(privateKey);
final var privateKeyCipher = keyFactory.generatePrivate(privateKeySpec);
// Fill the information to be signed with the data and private key.
final var signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(privateKeyCipher);
signature.update(data);
// Sign the data with the private key.
return signature.sign();
}
RSA Validate
private static boolean validate(final byte[] data, final byte[] publicKey, final byte[] signature) throws GeneralSecurityException {
// Get the public key encoded
final var keyFactory = KeyFactory.getInstance(RSA);
final var publicKeySpec = new X509EncodedKeySpec(publicKey);
final var publicKeyCipher = keyFactory.generatePublic(publicKeySpec);
// Fill the information to be signed with the data and public key.
final var verifier = Signature.getInstance(SIGN_ALGORITHM);
verifier.initVerify(publicKeyCipher);
verifier.update(data);
// Verify the data with signature and public key.
return verifier.verify(signature);
}