Skip to content

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));
    }
  1. Compute the Hash directly using SHA-256 algorithm.
  2. Apply a Salt to data before computing the Hash.
  3. 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();
    }
  1. Get the Key Generator RSA in Java crypto service provider (by default is SunJCE).
  2. 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 Mode
    public static final String SIGN_ALGORITHM = "SHA256withRSA";
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);
    }