Package org.syncany.crypto

Source Code of org.syncany.crypto.CipherUtil

/*
* Syncany, www.syncany.org
* Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.syncany.crypto;

import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER;
import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER_ID;
import static org.syncany.crypto.CipherParams.KEY_DERIVATION_DIGEST;
import static org.syncany.crypto.CipherParams.KEY_DERIVATION_INFO;
import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_FUNCTION;
import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_ROUNDS;
import static org.syncany.crypto.CipherParams.MASTER_KEY_SALT_SIZE;
import static org.syncany.crypto.CipherParams.MASTER_KEY_SIZE;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
import org.bouncycastle.crypto.params.HKDFParameters;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

/**
* The cipher utility provides functions to create a master key using PBKDF2,
* a derived key using SHA256, and to create a {@link Cipher} from a derived key.
* It furthermore offers a method to programmatically enable the unlimited strength
* crypto policies.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class CipherUtil {
  private static final Logger logger = Logger.getLogger(CipherUtil.class.getSimpleName())
 
  /**
   * Chars from A-Z / a-z to be used in randomly generated passwords.
   *
   * <p><b>Note:</b> This string cannot contain numbers, to prevent breaking
   * of the vector clock format.
   */
  private static final String ALPHABETIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
  private static AtomicBoolean initialized = new AtomicBoolean(false);
  private static AtomicBoolean unlimitedStrengthEnabled = new AtomicBoolean(false);
  private static SecureRandom secureRandom = new SecureRandom();

  static {
    init();
  }

  /**
   * Initializes the crypto provider ("Bouncy Castle") and tests whether the unlimited
   * strength policy has been enabled. Unlimited crypto allows for stronger crypto algorithms
   * such as AES-256 or Twofish-256.
   *
   * <p>The method is called in the <tt>static</tt> block of this class and hence initialized
   * whenever then class is used.
   *
   * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a>
   */
  public static synchronized void init() {
    if (!initialized.get()) {
      // Bouncy Castle
      if (Security.getProvider(CRYPTO_PROVIDER_ID) == null) {
        Security.addProvider(CRYPTO_PROVIDER);
      }

      // Unlimited strength
      try {
        unlimitedStrengthEnabled.set(Cipher.getMaxAllowedKeyLength("AES") > 128);
      }
      catch (Exception e) {
        unlimitedStrengthEnabled.set(false);
      }

      initialized.set(true);
    }
  }

  /**
   * Returns whether the unlimited strength policy is enabled in the current JVM.
   * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography
   *      Extension (JCE) Unlimited Strength</a>
   */
  public static boolean unlimitedStrengthEnabled() {
    return unlimitedStrengthEnabled.get();
  }

  /**
   * Attempts to programmatically enable the unlimited strength Java crypto extension
   * using the reflection API.
   *
   * <p>This class tries to set the property <tt>isRestricted</tt> of the class
   * <tt>javax.crypto.JceSecurity</tt> to <tt>false</tt> -- effectively disabling
   * the artificial limitations (and the disallowed algorithms).
   *
   * <p><b>Note</b>: Be aware that enabling the unlimited strength extension needs to
   * be acknowledged by the end-user to avoid legal issues! 
   *
   * @throws CipherException If the unlimited strength policy cannot be enabled.
   * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a>
   */
  public static void enableUnlimitedStrength() throws CipherException {
    if (!unlimitedStrengthEnabled.get()) {
      logger.log(Level.FINE, "- Enabling unlimited strength/crypto ...");

      try {
        Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");

        field.setAccessible(true);
        field.set(null, false);
      }
      catch (Exception e) {
        throw new CipherException(e);
      }
    }
  }

  /**
   * Creates a random array of bytes using the default {@link SecureRandom} implementation
   * of the currently active JVM. The returned array can be used as a basis for secret keys,
   * IVs or salts.
   *
   * @param size Size of the returned array (in bytes)
   * @return Returns a random byte array (using a secure pseudo random generator)
   */
  public static byte[] createRandomArray(int size) {
    byte[] randomByteArray = new byte[size];
    secureRandom.nextBytes(randomByteArray);

    return randomByteArray;
  }
 
    /**
     * Generates a random string the given length. Only uses characters
     * A-Z/a-z (in order to always create valid serialized vector clock representations).
     */
  public static String createRandomAlphabeticString(int size) {
    StringBuilder sb = new StringBuilder(size);
   
    for (int i = 0; i < size; i++) {
      sb.append(ALPHABETIC_CHARS.charAt(secureRandom.nextInt(ALPHABETIC_CHARS.length())));
    }
   
    return sb.toString();
  }

  /**
   * Creates a derived key from the given {@link SecretKey} an input salt and wraps the key in
   * a {@link SecretKeySpec} using the given {@link CipherSpec}.
   *
   * <p>This method simply uses the {@link #createDerivedKey(byte[], byte[], String, int) createDerivedKey()}
   * method using the encoded input key and the algorithm and key size given by the cipher spec.
   *
   * @param inputKey The source key to derive the new key from
   * @param inputSalt Input salt used to generate the new key (a non-secret random value!)
   * @param outputCipherSpec Defines the algorithm and key size of the new output key
   * @return Returns a derived key (including the given input salt)
   */
  public static SaltedSecretKey createDerivedKey(SecretKey inputKey, byte[] inputSalt, CipherSpec outputCipherSpec)
      throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
   
    return createDerivedKey(inputKey.getEncoded(), inputSalt, outputCipherSpec.getAlgorithm(), outputCipherSpec.getKeySize());
  }

  /**
   * Creates a derived key from the given input key material (raw byte array) and an input salt
   * and wraps the key in  a {@link SecretKeySpec} using the given output key algorithm and output
   * key size.
   *
   * <p>The algorithm used to derive the new key from the input key material (IKM) is the
   * <b>HMAC-based Extract-and-Expand Key Derivation Function (HKDF)</b> (see
   * <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>)
   *
   * @param inputKeyMaterial The input key material as raw data bytes, e.g. determined from {@link SecretKey#getEncoded()}
   * @param inputSalt Input salt used to generate the new key (a non-secret random value!)
   * @param outputKeyAlgorithm Defines the algorithm of the new output key (for {@link SecretKeySpec#getAlgorithm()})
   * @param outputKeySize Defines the key size of the new output key
   * @return Returns a new pseudorandom key derived from the input key material using HKDF
   * @see <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>
   */
  public static SaltedSecretKey createDerivedKey(byte[] inputKeyMaterial, byte[] inputSalt, String outputKeyAlgorithm, int outputKeySize)
      throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
   
    HKDFBytesGenerator hkdf = new HKDFBytesGenerator(KEY_DERIVATION_DIGEST);
    hkdf.init(new HKDFParameters(inputKeyMaterial, inputSalt, KEY_DERIVATION_INFO));

    byte[] derivedKey = new byte[outputKeySize / 8];
    hkdf.generateBytes(derivedKey, 0, derivedKey.length);

    return toSaltedSecretKey(derivedKey, inputSalt, outputKeyAlgorithm);
  }

  public static SecretKey toSecretKey(byte[] secretKeyBytes, String algorithm) {
    String plainAlgorithm = (algorithm.indexOf('/') != -1) ? algorithm.substring(0, algorithm.indexOf('/')) : algorithm;
    SecretKey secretKey = new SecretKeySpec(secretKeyBytes, plainAlgorithm);

    return secretKey;
  }

  public static SaltedSecretKey toSaltedSecretKey(byte[] secretKeyBytes, byte[] saltBytes, String algorithm) {
    return new SaltedSecretKey(toSecretKey(secretKeyBytes, algorithm), saltBytes);
  }

  public static SaltedSecretKey createMasterKey(String password) throws CipherException {
    byte[] salt = createRandomArray(MASTER_KEY_SALT_SIZE / 8);
    return createMasterKey(password, salt);
  }

  public static SaltedSecretKey createMasterKey(String password, byte[] salt) throws CipherException {
    try {
      logger.log(Level.FINE, "- Creating secret key using {0} with {1} rounds, key size {2} bit ...", new Object[] { MASTER_KEY_DERIVATION_FUNCTION,
          MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE });
 
      SecretKeyFactory factory = SecretKeyFactory.getInstance(MASTER_KEY_DERIVATION_FUNCTION);
      KeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE);
      SecretKey masterKey = factory.generateSecret(pbeKeySpec);
 
      return new SaltedSecretKey(masterKey, salt);
    }
    catch (Exception e) {
      throw new CipherException(e);
    }
  }

  public static boolean isEncrypted(File file) throws IOException {
    byte[] actualMagic = new byte[MultiCipherOutputStream.STREAM_MAGIC.length];

    RandomAccessFile rFile = new RandomAccessFile(file, "r");
    rFile.read(actualMagic);
    rFile.close();

    return Arrays.equals(actualMagic, MultiCipherOutputStream.STREAM_MAGIC);
  }

  public static void encrypt(InputStream plaintextInputStream, OutputStream ciphertextOutputStream, List<CipherSpec> cipherSpecs,
      SaltedSecretKey masterKey) throws CipherException {

    try {
      CipherSession cipherSession = new CipherSession(masterKey);
      OutputStream multiCipherOutputStream = new MultiCipherOutputStream(ciphertextOutputStream, cipherSpecs, cipherSession);
 
      int read = -1;
      byte[] buffer = new byte[4096];
 
      while (-1 != (read = plaintextInputStream.read(buffer))) {
        multiCipherOutputStream.write(buffer, 0, read);
      }
     
      plaintextInputStream.close();
      multiCipherOutputStream.close();
    }
    catch (IOException e) {
      throw new CipherException(e);
    }
  }

  public static byte[] encrypt(InputStream plaintextInputStream, List<CipherSpec> cipherSuites, SaltedSecretKey masterKey) throws CipherException {
    ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream();
    encrypt(plaintextInputStream, ciphertextOutputStream, cipherSuites, masterKey);

    return ciphertextOutputStream.toByteArray();
  }

  public static byte[] decrypt(InputStream fromInputStream, SaltedSecretKey masterKey) throws CipherException {
    try {
      CipherSession cipherSession = new CipherSession(masterKey);
      MultiCipherInputStream multiCipherInputStream = new MultiCipherInputStream(fromInputStream, cipherSession);
      ByteArrayOutputStream plaintextOutputStream = new ByteArrayOutputStream();
 
      int read = -1;
      byte[] buffer = new byte[4096];
 
      while (-1 != (read = multiCipherInputStream.read(buffer))) {
        plaintextOutputStream.write(buffer, 0, read);
      }
     
      multiCipherInputStream.close();
      plaintextOutputStream.close();
 
      return plaintextOutputStream.toByteArray();
    }
    catch (IOException e) {
      throw new CipherException(e);
    }
  }   
 
  /**
   * Generates a 2048-bit RSA key pair.
   */
  public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException, CipherException, NoSuchProviderException {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CipherParams.CERTIFICATE_KEYPAIR_ALGORITHM, CipherParams.CRYPTO_PROVIDER_ID);
    keyGen.initialize(CipherParams.CERTIFICATE_KEYPAIR_SIZE, secureRandom);
   
    return keyGen.generateKeyPair();   
  }
 
  /**
   * Generates a self-signed certificate, given a public/private key pair.
   *
   * @see https://code.google.com/p/gitblit/source/browse/src/com/gitblit/MakeCertificate.java?r=88598bb2f779b73479512d818c675dea8fa72138
   */
  public static X509Certificate generateSelfSignedCertificate(String commonName, KeyPair keyPair) throws OperatorCreationException, CertificateException,
      InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
   
    // Certificate CN, O and OU
    X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
   
    builder.addRDN(BCStyle.CN, commonName);
    builder.addRDN(BCStyle.O, CipherParams.CERTIFICATE_ORGANIZATION);
    builder.addRDN(BCStyle.OU, CipherParams.CERTIFICATE_ORGUNIT);

    // Dates and serial
    Date notBefore = new Date(System.currentTimeMillis() - 1*24*60*60*1000L);
    Date notAfter = new Date(System.currentTimeMillis() + 5*365*24*60*60*1000L);
    BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());

    // Issuer and subject (identical, because self-signed)
    X500Name issuer = builder.build();
    X500Name subject = issuer;
   
    X509v3CertificateBuilder certificateGenerator =
      new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, keyPair.getPublic());
   
    ContentSigner signatureGenerator = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
      .setProvider(CipherParams.CRYPTO_PROVIDER)
      .build(keyPair.getPrivate());
   
    X509Certificate certificate = new JcaX509CertificateConverter()
      .setProvider(CipherParams.CRYPTO_PROVIDER)
      .getCertificate(certificateGenerator.build(signatureGenerator));
   
    certificate.checkValidity(new Date());
    certificate.verify(certificate.getPublicKey());

    return certificate;     
  }
 
  /**
   * Creates an SSL context, given a key store and a trust store.
   */
  public static SSLContext createSSLContext(KeyStore keyStore, KeyStore trustStore) throws Exception {
    try {
      // Server key and certificate
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
      keyManagerFactory.init(keyStore, new char[0]);

      KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

      // Trusted certificates
      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
      trustManagerFactory.init(trustStore);
     
      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

      // Create SSL context
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(keyManagers, trustManagers, null);

      return sslContext;
    }
    catch (Exception e) {
      throw new Exception("Unable to initialize SSL context", e);
    }
  }
}
TOP

Related Classes of org.syncany.crypto.CipherUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.