All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.owasp.esapi.crypto.CryptoHelper Maven / Gradle / Ivy

Go to download

The Enterprise Security API (ESAPI) project is an OWASP project to create simple strong security controls for every web platform. Security controls are not simple to build. You can read about the hundreds of pitfalls for unwary developers on the OWASP website. By providing developers with a set of strong controls, we aim to eliminate some of the complexity of creating secure web applications. This can result in significant cost savings across the SDLC.

There is a newer version: 2.5.5.0
Show newest version
/*
 * OWASP Enterprise Security API (ESAPI)
 * 
 * This file is part of the Open Web Application Security Project (OWASP)
 * Enterprise Security API (ESAPI) project. For details, please see
 * http://www.owasp.org/index.php/ESAPI.
 *
 * Copyright (c) 2009 - The OWASP Foundation
 */
package org.owasp.esapi.crypto;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Logger;
import org.owasp.esapi.errors.EncryptionException;

/**
 * Class to provide some convenience methods for encryption, decryption, etc.
 * 

* All the cryptographic operations use the default cryptographic properties; * e.g., default cipher transformation, default key size, default IV type (where * applicable), etc. * * @author [email protected] * @since 2.0 */ public class CryptoHelper { private static final Logger logger = ESAPI.getLogger("CryptoHelper"); // TODO: Also consider supplying implementation of RFC 2898 / PKCS#5 PBKDF2 // in this file as well??? Maybe save for ESAPI 2.1 or 3.0. /** * Generate a random secret key appropriate to the specified cipher algorithm * and key size. * @param alg The cipher algorithm or cipher transformation. (If the latter is * passed, the cipher algorithm is determined from it.) Cannot be * null or empty. * @param keySize The key size, in bits. * @return A random {@code SecretKey} is returned. * @throws EncryptionException Thrown if cannot create secret key conforming to * requested algorithm with requested size. Typically this is caused by * specifying an unavailable algorithm or invalid key size. */ public static SecretKey generateSecretKey(String alg, int keySize) throws EncryptionException { assert alg != null : "Algorithm must not be null."; // NPE if null and assertions disabled. assert !alg.equals("") : "Algorithm must not be empty"; // NoSuchAlgorithmExeption if empty & assertions disabled. assert keySize > 0 : "Key size must be positive."; // Usually should be even multiple of 8, but not strictly required by alg. // Don't use CipherSpec here to get algorithm as this may cause assertion // to fail (when enabled) if only algorithm name is passed to us. String[] cipherSpec = alg.split("/"); String cipherAlg = cipherSpec[0]; try { // Special case for things like PBEWithMD5AndDES or PBEWithSHA1AndDESede. // In such cases, the key generator should only request an instance of "PBE". if ( cipherAlg.toUpperCase().startsWith("PBEWITH") ) { cipherAlg = "PBE"; } KeyGenerator kgen = KeyGenerator.getInstance( cipherAlg ); kgen.init(keySize); return kgen.generateKey(); } catch (NoSuchAlgorithmException e) { throw new EncryptionException("Failed to generate random secret key", "Invalid algorithm. Failed to generate secret key for " + alg + " with size of " + keySize + " bits.", e); } catch (InvalidParameterException e) { throw new EncryptionException("Failed to generate random secret key - invalid key size specified.", "Invalid key size. Failed to generate secret key for " + alg + " with size of " + keySize + " bits.", e); } } /** * The method is ESAPI's Key Derivation Function (KDF) that computes a * derived key from the {@code keyDerivationKey} for either * encryption / decryption or for authentication. *

* CAUTION: If this algorithm for computing derived keys from the * key derivation key is ever changed, we risk breaking backward compatibility of being * able to decrypt data previously encrypted with earlier / different versions * of this method. Therefore, do not change this unless you are 100% certain that * what you are doing will NOT change either of the derived keys for * ANY "key derivation key" AT ALL!!! *

* NOTE: This method is generally not intended to be called separately. * It is used by ESAPI's reference crypto implementation class {@code JavaEncryptor} * and might be useful for someone implementing their own replacement class, but * generally it is not something that is useful to application client code. * * @param keyDerivationKey A key used as an input to a key derivation function * to derive other keys. This is the key that generally * is created using some key generation mechanism such as * {@link #generateSecretKey(String, int)}. The * "input" key from which the other keys are derived. * The derived key will have the same algorithm type * as this key. * @param keySize The cipher's key size (in bits) for the {@code keyDerivationKey}. * Must have a minimum size of 56 bits and be an integral multiple of 8-bits. * Note: The derived key will have the same size as this. * @param purpose The purpose for the derived key. Must be either the * string "encryption" or "authenticity". Use "encryption" for * creating a derived key to use for confidentiality, and "authenticity" * for a derived key to use with a MAC to ensure message authenticity. * @return The derived {@code SecretKey} to be used according * to the specified purpose. Note that this serves the same purpose * as "label" in section 5.1 of NIST SP 800-108. * @throws NoSuchAlgorithmException The {@code keyDerivationKey} has an unsupported * encryption algorithm or no current JCE provider supports * "HmacSHA1". * @throws EncryptionException If "UTF-8" is not supported as an encoding, then * this is thrown with the original {@code UnsupportedEncodingException} * as the cause. (NOTE: This should never happen as "UTF-8" is supposed to * be a common encoding supported by all Java implementations. Support * for it is usually in rt.jar.) * @throws InvalidKeyException Likely indicates a coding error. Should not happen. * @throws EncryptionException Throw for some precondition violations. * @deprecated Use{@code KeyDerivationFunction} instead. This method will be removed as of * ESAPI release 2.1 so if you are using this, please change your code. */ public static SecretKey computeDerivedKey(SecretKey keyDerivationKey, int keySize, String purpose) throws NoSuchAlgorithmException, InvalidKeyException, EncryptionException { // These really should be turned into actual runtime checks and an // IllegalArgumentException should be thrown if they are violated. assert keyDerivationKey != null : "Key derivation key cannot be null."; // We would choose a larger minimum key size, but we want to be // able to accept DES for legacy encryption needs. assert keySize >= 56 : "Key has size of " + keySize + ", which is less than minimum of 56-bits."; assert (keySize % 8) == 0 : "Key size (" + keySize + ") must be a even multiple of 8-bits."; assert purpose != null; assert purpose.equals("encryption") || purpose.equals("authenticity") : "Purpose must be \"encryption\" or \"authenticity\"."; // DISCUSS: Should we use HmacSHA1 (what we were using) or the HMAC defined by // Encryptor.KDF.PRF instead? Either way, this is not compatible with // previous ESAPI versions. JavaEncryptor doesn't use this any longer. KeyDerivationFunction kdf = new KeyDerivationFunction( KeyDerivationFunction.PRF_ALGORITHMS.HmacSHA1); return kdf.computeDerivedKey(keyDerivationKey, keySize, purpose); } /** * Return true if specified cipher mode is one of those specified in the * {@code ESAPI.properties} file that supports both confidentiality * and authenticity (i.e., a "combined cipher mode" as NIST refers * to it). * @param cipherMode The specified cipher mode to be used for the encryption * or decryption operation. * @return true if the specified cipher mode is in the comma-separated list * of cipher modes supporting both confidentiality and authenticity; * otherwise false. * @see org.owasp.esapi.SecurityConfiguration#getCombinedCipherModes() */ public static boolean isCombinedCipherMode(String cipherMode) { assert cipherMode != null : "Cipher mode may not be null"; assert ! cipherMode.equals("") : "Cipher mode may not be empty string"; List combinedCipherModes = ESAPI.securityConfiguration().getCombinedCipherModes(); return combinedCipherModes.contains( cipherMode ); } /** * Return true if specified cipher mode is one that may be used for * encryption / decryption operations via {@link org.owasp.esapi.Encryptor}. * @param cipherMode The specified cipher mode to be used for the encryption * or decryption operation. * @return true if the specified cipher mode is in the comma-separated list * of cipher modes supporting both confidentiality and authenticity; * otherwise false. * @see #isCombinedCipherMode(String) * @see org.owasp.esapi.SecurityConfiguration#getCombinedCipherModes() * @see org.owasp.esapi.SecurityConfiguration#getAdditionalAllowedCipherModes() */ public static boolean isAllowedCipherMode(String cipherMode) { if ( isCombinedCipherMode(cipherMode) ) { return true; } List extraCipherModes = ESAPI.securityConfiguration().getAdditionalAllowedCipherModes(); return extraCipherModes.contains( cipherMode ); } /** * Check to see if a Message Authentication Code (MAC) is required * for a given {@code CipherText} object and the current ESAPI.property * settings. A MAC is considered "required" if the specified * {@code CipherText} was not encrypted by one of the preferred * "combined" cipher modes (e.g., CCM or GCM) and the setting of the * current ESAPI properties for the property * {@code Encryptor.CipherText.useMAC} is set to {@code true}. (Normally, * the setting for {@code Encryptor.CipherText.useMAC} should be set to * {@code true} unless FIPS 140-2 compliance is required. See * * User Guide for Symmetric Encryption in ESAPI 2.0 and the section * on using ESAPI with FIPS for further details. * * @param ct The specified {@code CipherText} object to check to see if * it requires a MAC. * @returns True if a MAC is required, false if it is not required. */ public static boolean isMACRequired(CipherText ct) { boolean preferredCipherMode = CryptoHelper.isCombinedCipherMode( ct.getCipherMode() ); boolean wantsMAC = ESAPI.securityConfiguration().useMACforCipherText(); // The preferred "combined" cipher modes such as CCM, GCM, etc. do // not require a MAC as a MAC would be superfluous and just require // additional computing time. return ( !preferredCipherMode && wantsMAC ); } /** * If a Message Authentication Code (MAC) is required for the specified * {@code CipherText} object, then attempt to validate the MAC that * should be embedded within the {@code CipherText} object by using a * derived key based on the specified {@code SecretKey}. * * @param sk The {@code SecretKey} used to derived a key to check * the authenticity via the MAC. * @param ct The {@code CipherText} that we are checking for a * valid MAC. * * @return True is returned if a MAC is required and it is valid as * verified using a key derived from the specified * {@code SecretKey} or a MAC is not required. False is returned * otherwise. */ public static boolean isCipherTextMACvalid(SecretKey sk, CipherText ct) { if ( CryptoHelper.isMACRequired( ct ) ) { try { SecretKey authKey = CryptoHelper.computeDerivedKey( sk, ct.getKeySize(), "authenticity"); boolean validMAC = ct.validateMAC( authKey ); return validMAC; } catch (Exception ex) { // Error on side of security. If this fails and can't verify MAC // assume it is invalid. Note that CipherText.toString() does not // print the actual ciphertext. logger.warning(Logger.SECURITY_FAILURE, "Unable to validate MAC for ciphertext " + ct, ex); return false; } } return true; } /** * Overwrite a byte array with a specified byte. This is frequently done * to a plaintext byte array so the sensitive data is not lying around * exposed in memory. * @param bytes The byte array to be overwritten. * @param x The byte array {@code bytes} is overwritten with this byte. */ public static void overwrite(byte[] bytes, byte x) { Arrays.fill(bytes, x); } /** * Overwrite a byte array with the byte containing '*'. That is, call *

	 * 		overwrite(bytes, (byte)'*');
	 * 
* @param bytes The byte array to be overwritten. */ public static void overwrite(byte[] bytes) { overwrite(bytes, (byte)'*'); } // These provide for a bit more type safety when copying bytes around. /** * Same as {@code System.arraycopy(src, 0, dest, 0, length)}. * * @param src the source array. * @param dest the destination array. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception NullPointerException if either src or * dest is null. */ public static void copyByteArray(final byte[] src, byte[] dest, int length) { System.arraycopy(src, 0, dest, 0, length); } /** * Same as {@code copyByteArray(src, dest, src.length)}. * @param src the source array. * @param dest the destination array. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception NullPointerException if either src or * dest is null. */ public static void copyByteArray(final byte[] src, byte[] dest) { copyByteArray(src, dest, src.length); } /** * A "safe" array comparison that is not vulnerable to side-channel * "timing attacks". All comparisons of non-null, equal length bytes should * take same amount of time. We use this for cryptographic comparisons. * * @param b1 A byte array to compare. * @param b2 A second byte array to compare. * @return {@code true} if both byte arrays are null or if both byte * arrays are identical or have the same value; otherwise * {@code false} is returned. */ public static boolean arrayCompare(byte[] b1, byte[] b2) { if ( b1 == b2 ) { return true; } if ( b1 == null || b2 == null ) { return (b1 == b2); } if ( b1.length != b2.length ) { return false; } int result = 0; // Make sure to go through ALL the bytes. We use the fact that if // you XOR any bit stream with itself the result will be all 0 bits, // which in turn yields 0 for the result. for(int i = 0; i < b1.length; i++) { // XOR the 2 current bytes and then OR with the outstanding result. result |= (b1[i] ^ b2[i]); } return (result == 0) ? true : false; } /** * Prevent public, no-argument CTOR from being auto-generated. Public CTOR * is not needed as all methods are static. */ private CryptoHelper() { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy