eu.dety.burp.joseph.utilities.Crypto.java Source code

Java tutorial

Introduction

Here is the source code for eu.dety.burp.joseph.utilities.Crypto.java

Source

/**
 * JOSEPH - JavaScript Object Signing and Encryption Pentesting Helper
 * Copyright (C) 2016 Dennis Detering
 * <p>
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * <p>
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package eu.dety.burp.joseph.utilities;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;

/**
 * Help functions to perform cryptographic operations.
 *
 * @author Dennis Detering
 * @version 1.0
 */
public class Crypto {

    public static final List<String> JWS_HMAC_ALGS = Arrays.asList("HS256", "HS384", "HS512");

    /**
     * Get MAC algorithm name for Java by JOSE algorithm name
     *
     * @param algorithm
     *            Algorithm name as string
     * @param fallback
     *            Fallback return value if none of the defined match
     * @return MAC algorithm name as used by Java
     */
    public static String getMacAlgorithmByJoseAlgorithm(String algorithm, String fallback) {
        switch (algorithm) {
        case "HS256":
            return "HmacSHA256";
        case "HS384":
            return "HmacSHA384";
        case "HS512":
            return "HmacSHA512";
        default:
            return fallback;
        }
    }

    /**
     * Get key length needed for JOSE operation by JOSE algorithm name
     *
     * @param algorithm
     *            Algorithm name as string
     * @param fallback
     *            Fallback return value if none of the defined match
     * @return Key length for JOSE operation
     */
    public static int getJoseKeyLengthByJoseAlgorithm(String algorithm, int fallback) {
        switch (algorithm) {
        case "A128GCM":
            return 16;
        case "A192GCM":
            return 24;
        case "A128CBC-HS256":
        case "A256GCM":
            return 32;
        case "A192CBC-HS384":
            return 48;
        case "A256CBC-HS512":
            return 64;
        default:
            return fallback;
        }
    }

    /**
     * Get key length needed for AES operation by JOSE algorithm name
     *
     * @param algorithm
     *            Algorithm name as string
     * @param fallback
     *            Fallback return value if none of the defined match
     * @return Key length for AES operation
     */
    public static int getAesKeyLengthByJoseAlgorithm(String algorithm, int fallback) {
        switch (algorithm) {
        case "A128GCM":
        case "A128CBC-HS256":
            return 16;
        case "A192GCM":
        case "A192CBC-HS384":
            return 24;
        case "A256GCM":
        case "A256CBC-HS512":
            return 32;
        default:
            return fallback;
        }
    }

    /**
     * Generate MAC
     *
     * @param algorithm
     *            Algorithm name as string
     * @param key
     *            Symmetric key as byte array
     * @param message
     *            Input message as byte array
     * @return Key length for JOSE operation
     */
    public static byte[] generateMac(String algorithm, byte[] key, byte[] message) {
        try {
            Mac mac = Mac.getInstance(algorithm);
            SecretKeySpec secret_key = new SecretKeySpec(key, algorithm);
            mac.init(secret_key);

            return mac.doFinal(message);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Decrypt AES ciphertext
     *
     * @param header
     *            JOSE header
     * @param key
     *            Symmetric key as byte array
     * @param iv
     *            Initialization Vector as byte array
     * @param cipherBytes
     *            Ciphertext as byte array
     * @param authTag
     *            Authentication tag as byte array
     * @throws DecryptionFailedException
     * @return Decrypted message as byte array
     */
    public static byte[] decryptAES(String header, byte[] key, byte[] iv, byte[] cipherBytes, byte[] authTag)
            throws DecryptionFailedException {
        byte[] decryptedContent;

        String encAlg = Decoder.getValueByBase64String(header, "enc").toUpperCase();

        int keyLen = getAesKeyLengthByJoseAlgorithm(encAlg, 32);
        String cipherInstance;

        switch (encAlg) {
        case "A128CBC-HS256":
            cipherInstance = "AES/CBC/PKCS5Padding";
            break;
        case "A192CBC-HS384":
            cipherInstance = "AES/CBC/PKCS5Padding";
            break;
        case "A256CBC-HS512":
            cipherInstance = "AES/CBC/PKCS5Padding";
            break;
        case "A128GCM":
            cipherInstance = "AES/GCM/NoPadding";
            break;
        case "A192GCM":
            cipherInstance = "AES/GCM/NoPadding";
            break;
        case "A256GCM":
            cipherInstance = "AES/GCM/NoPadding";
            break;
        default:
            throw new DecryptionFailedException("Could not determine encryption algorithm or it is not supported");
        }

        byte[] keyBytes = Arrays.copyOfRange(key, key.length - keyLen, key.length);

        SecretKey aesKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher;

        try {
            // TODO move this to some general library initialization code
            removeCryptoStrengthRestriction();

            cipher = Cipher.getInstance(cipherInstance, new BouncyCastleProvider());
            cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));

            if (encAlg.contains("GCM")) {
                cipher.updateAAD(header.getBytes());

                // Concatenate ciphertext and authentication tag byte arrays
                byte[] concat = new byte[cipherBytes.length + authTag.length];
                System.arraycopy(cipherBytes, 0, concat, 0, cipherBytes.length);
                System.arraycopy(authTag, 0, concat, cipherBytes.length, authTag.length);

                decryptedContent = cipher.doFinal(concat);
            } else {
                decryptedContent = cipher.doFinal(cipherBytes);
            }

        } catch (Exception e) {
            throw new DecryptionFailedException(e.getMessage());
        }

        return decryptedContent;

    }

    /**
     * Removes JDK crypto restriction.
     *
     * Partially taken from:
     * https://github.com/jruby/jruby/blob/0c345e1b186bd457ebd96143c0816abe93b18fdf/core/src/main/java/org/jruby/util/SecurityHelper.java
     */
    public static void removeCryptoStrengthRestriction() {
        try {
            if (Cipher.getMaxAllowedKeyLength("AES") < 256) {
                Class jceSecurity = Class.forName("javax.crypto.JceSecurity");
                Field isRestricted = jceSecurity.getDeclaredField("isRestricted");
                if (Modifier.isFinal(isRestricted.getModifiers())) {
                    Field modifiers = Field.class.getDeclaredField("modifiers");
                    modifiers.setAccessible(true);
                    modifiers.setInt(isRestricted, isRestricted.getModifiers() & ~Modifier.FINAL);
                    modifiers.setAccessible(false);
                }
                isRestricted.setAccessible(true);
                isRestricted.setBoolean(null, false);
                isRestricted.setAccessible(false);
            }
        } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException
                | NoSuchAlgorithmException | NoSuchFieldException | SecurityException ex) {
            throw new SecurityException("It is not possible to use unrestricted policy with this JDK, "
                    + "consider reconfiguration: " + ex.getLocalizedMessage(), ex);
        }
    }
}