001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.crypto; 019 020import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER; 021import static org.syncany.crypto.CipherParams.CRYPTO_PROVIDER_ID; 022import static org.syncany.crypto.CipherParams.KEY_DERIVATION_DIGEST; 023import static org.syncany.crypto.CipherParams.KEY_DERIVATION_INFO; 024import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_FUNCTION; 025import static org.syncany.crypto.CipherParams.MASTER_KEY_DERIVATION_ROUNDS; 026import static org.syncany.crypto.CipherParams.MASTER_KEY_SALT_SIZE; 027import static org.syncany.crypto.CipherParams.MASTER_KEY_SIZE; 028 029import java.io.ByteArrayOutputStream; 030import java.io.File; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.OutputStream; 034import java.io.RandomAccessFile; 035import java.lang.reflect.Field; 036import java.lang.reflect.Modifier; 037import java.math.BigInteger; 038import java.security.InvalidKeyException; 039import java.security.KeyPair; 040import java.security.KeyPairGenerator; 041import java.security.KeyStore; 042import java.security.NoSuchAlgorithmException; 043import java.security.NoSuchProviderException; 044import java.security.SecureRandom; 045import java.security.Security; 046import java.security.SignatureException; 047import java.security.cert.CertificateException; 048import java.security.cert.X509Certificate; 049import java.security.spec.InvalidKeySpecException; 050import java.security.spec.KeySpec; 051import java.util.Arrays; 052import java.util.Date; 053import java.util.List; 054import java.util.concurrent.atomic.AtomicBoolean; 055import java.util.logging.Level; 056import java.util.logging.Logger; 057 058import javax.crypto.Cipher; 059import javax.crypto.SecretKey; 060import javax.crypto.SecretKeyFactory; 061import javax.crypto.spec.PBEKeySpec; 062import javax.crypto.spec.SecretKeySpec; 063import javax.net.ssl.KeyManager; 064import javax.net.ssl.KeyManagerFactory; 065import javax.net.ssl.SSLContext; 066import javax.net.ssl.TrustManager; 067import javax.net.ssl.TrustManagerFactory; 068 069import org.bouncycastle.asn1.x500.X500Name; 070import org.bouncycastle.asn1.x500.X500NameBuilder; 071import org.bouncycastle.asn1.x500.style.BCStyle; 072import org.bouncycastle.cert.X509v3CertificateBuilder; 073import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 074import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; 075import org.bouncycastle.crypto.generators.HKDFBytesGenerator; 076import org.bouncycastle.crypto.params.HKDFParameters; 077import org.bouncycastle.operator.ContentSigner; 078import org.bouncycastle.operator.OperatorCreationException; 079import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 080 081/** 082 * The cipher utility provides functions to create a master key using PBKDF2, 083 * a derived key using SHA256, and to create a {@link Cipher} from a derived key. 084 * It furthermore offers a method to programmatically enable the unlimited strength 085 * crypto policies. 086 * 087 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 088 */ 089public class CipherUtil { 090 private static final Logger logger = Logger.getLogger(CipherUtil.class.getSimpleName()); 091 092 /** 093 * Chars from A-Z / a-z to be used in randomly generated passwords. 094 * 095 * <p><b>Note:</b> This string cannot contain numbers, to prevent breaking 096 * of the vector clock format. 097 */ 098 private static final String ALPHABETIC_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 099 100 private static AtomicBoolean initialized = new AtomicBoolean(false); 101 private static AtomicBoolean unlimitedStrengthEnabled = new AtomicBoolean(false); 102 private static SecureRandom secureRandom = new SecureRandom(); 103 104 static { 105 init(); 106 } 107 108 /** 109 * Initializes the crypto provider ("Bouncy Castle") and tests whether the unlimited 110 * strength policy has been enabled. Unlimited crypto allows for stronger crypto algorithms 111 * such as AES-256 or Twofish-256. 112 * 113 * <p>The method is called in the <code>static</code> block of this class and hence initialized 114 * whenever then class is used. 115 * 116 * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a> 117 */ 118 public static synchronized void init() { 119 if (!initialized.get()) { 120 // Bouncy Castle 121 if (Security.getProvider(CRYPTO_PROVIDER_ID) == null) { 122 Security.addProvider(CRYPTO_PROVIDER); 123 } 124 125 // Unlimited strength 126 try { 127 unlimitedStrengthEnabled.set(Cipher.getMaxAllowedKeyLength("AES") > 128); 128 } 129 catch (NoSuchAlgorithmException e) { 130 logger.log(Level.FINE, "No such transformation found", e); 131 unlimitedStrengthEnabled.set(false); 132 } 133 134 initialized.set(true); 135 } 136 } 137 138 /** 139 * Attempts to programmatically enable the unlimited strength Java crypto extension 140 * using the reflection API. 141 * 142 * <p>This class tries to set the property <code>isRestricted</code> of the class 143 * <code>javax.crypto.JceSecurity</code> to <code>false</code> -- effectively disabling 144 * the artificial limitations (and the disallowed algorithms). 145 * 146 * <p><b>Note</b>: Be aware that enabling the unlimited strength extension needs to 147 * be acknowledged by the end-user to avoid legal issues! 148 * 149 * @throws CipherException If the unlimited strength policy cannot be enabled. 150 * @see <a href="www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html">Java Cryptography Extension (JCE) Unlimited Strength</a> 151 */ 152 public static void enableUnlimitedStrength() throws CipherException { 153 if (!unlimitedStrengthEnabled.get()) { 154 logger.log(Level.FINE, "- Enabling unlimited strength/crypto ..."); 155 156 /* 157 * We want to turn off the isRestricted field. However it is a 158 * private final field. We therefore use reflection. 159 */ 160 try { 161 Field isRestrictedField = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted"); 162 isRestrictedField.setAccessible(true); 163 Field modifiersField = Field.class.getDeclaredField("modifiers"); 164 modifiersField.setAccessible(true); 165 modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL); 166 isRestrictedField.set(null, false); 167 } 168 catch (Exception e) { 169 throw new CipherException(e); 170 } 171 } 172 } 173 174 /** 175 * Creates a random array of bytes using the default {@link SecureRandom} implementation 176 * of the currently active JVM. The returned array can be used as a basis for secret keys, 177 * IVs or salts. 178 * 179 * @param size Size of the returned array (in bytes) 180 * @return Returns a random byte array (using a secure pseudo random generator) 181 */ 182 public static byte[] createRandomArray(int size) { 183 byte[] randomByteArray = new byte[size]; 184 secureRandom.nextBytes(randomByteArray); 185 186 return randomByteArray; 187 } 188 189 /** 190 * Generates a random string the given length. Only uses characters 191 * A-Z/a-z (in order to always create valid serialized vector clock representations). 192 */ 193 public static String createRandomAlphabeticString(int size) { 194 StringBuilder sb = new StringBuilder(size); 195 196 for (int i = 0; i < size; i++) { 197 sb.append(ALPHABETIC_CHARS.charAt(secureRandom.nextInt(ALPHABETIC_CHARS.length()))); 198 } 199 200 return sb.toString(); 201 } 202 203 /** 204 * Creates a derived key from the given {@link SecretKey} an input salt and wraps the key in 205 * a {@link SecretKeySpec} using the given {@link CipherSpec}. 206 * 207 * <p>This method simply uses the {@link #createDerivedKey(byte[], byte[], String, int) createDerivedKey()} 208 * method using the encoded input key and the algorithm and key size given by the cipher spec. 209 * 210 * @param inputKey The source key to derive the new key from 211 * @param inputSalt Input salt used to generate the new key (a non-secret random value!) 212 * @param outputCipherSpec Defines the algorithm and key size of the new output key 213 * @return Returns a derived key (including the given input salt) 214 */ 215 public static SaltedSecretKey createDerivedKey(SecretKey inputKey, byte[] inputSalt, CipherSpec outputCipherSpec) 216 throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { 217 218 return createDerivedKey(inputKey.getEncoded(), inputSalt, outputCipherSpec.getAlgorithm(), outputCipherSpec.getKeySize()); 219 } 220 221 /** 222 * Creates a derived key from the given input key material (raw byte array) and an input salt 223 * and wraps the key in a {@link SecretKeySpec} using the given output key algorithm and output 224 * key size. 225 * 226 * <p>The algorithm used to derive the new key from the input key material (IKM) is the 227 * <b>HMAC-based Extract-and-Expand Key Derivation Function (HKDF)</b> (see 228 * <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a>) 229 * 230 * @param inputKeyMaterial The input key material as raw data bytes, e.g. determined from {@link SecretKey#getEncoded()} 231 * @param inputSalt Input salt used to generate the new key (a non-secret random value!) 232 * @param outputKeyAlgorithm Defines the algorithm of the new output key (for {@link SecretKeySpec#getAlgorithm()}) 233 * @param outputKeySize Defines the key size of the new output key 234 * @return Returns a new pseudorandom key derived from the input key material using HKDF 235 * @see <a href="http://tools.ietf.org/html/rfc5869">RFC 5869</a> 236 */ 237 public static SaltedSecretKey createDerivedKey(byte[] inputKeyMaterial, byte[] inputSalt, String outputKeyAlgorithm, int outputKeySize) 238 throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { 239 240 HKDFBytesGenerator hkdf = new HKDFBytesGenerator(KEY_DERIVATION_DIGEST); 241 hkdf.init(new HKDFParameters(inputKeyMaterial, inputSalt, KEY_DERIVATION_INFO)); 242 243 byte[] derivedKey = new byte[outputKeySize / 8]; 244 hkdf.generateBytes(derivedKey, 0, derivedKey.length); 245 246 return toSaltedSecretKey(derivedKey, inputSalt, outputKeyAlgorithm); 247 } 248 249 public static SecretKey toSecretKey(byte[] secretKeyBytes, String algorithm) { 250 String plainAlgorithm = (algorithm.indexOf('/') != -1) ? algorithm.substring(0, algorithm.indexOf('/')) : algorithm; 251 SecretKey secretKey = new SecretKeySpec(secretKeyBytes, plainAlgorithm); 252 253 return secretKey; 254 } 255 256 public static SaltedSecretKey toSaltedSecretKey(byte[] secretKeyBytes, byte[] saltBytes, String algorithm) { 257 return new SaltedSecretKey(toSecretKey(secretKeyBytes, algorithm), saltBytes); 258 } 259 260 public static SaltedSecretKey createMasterKey(String password) throws CipherException { 261 byte[] salt = createRandomArray(MASTER_KEY_SALT_SIZE / 8); 262 return createMasterKey(password, salt); 263 } 264 265 public static SaltedSecretKey createMasterKey(String password, byte[] salt) throws CipherException { 266 try { 267 logger.log(Level.FINE, "- Creating secret key using {0} with {1} rounds, key size {2} bit ...", new Object[] { 268 MASTER_KEY_DERIVATION_FUNCTION, 269 MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE }); 270 271 SecretKeyFactory factory = SecretKeyFactory.getInstance(MASTER_KEY_DERIVATION_FUNCTION); 272 KeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, MASTER_KEY_DERIVATION_ROUNDS, MASTER_KEY_SIZE); 273 SecretKey masterKey = factory.generateSecret(pbeKeySpec); 274 275 return new SaltedSecretKey(masterKey, salt); 276 } 277 catch (Exception e) { 278 throw new CipherException(e); 279 } 280 } 281 282 public static boolean isEncrypted(File file) throws IOException { 283 byte[] actualMagic = new byte[MultiCipherOutputStream.STREAM_MAGIC.length]; 284 285 RandomAccessFile rFile = new RandomAccessFile(file, "r"); 286 rFile.read(actualMagic); 287 rFile.close(); 288 289 return Arrays.equals(actualMagic, MultiCipherOutputStream.STREAM_MAGIC); 290 } 291 292 public static void encrypt(InputStream plaintextInputStream, OutputStream ciphertextOutputStream, List<CipherSpec> cipherSpecs, 293 SaltedSecretKey masterKey) throws CipherException { 294 295 try { 296 CipherSession cipherSession = new CipherSession(masterKey); 297 OutputStream multiCipherOutputStream = new MultiCipherOutputStream(ciphertextOutputStream, cipherSpecs, cipherSession); 298 299 int read = -1; 300 byte[] buffer = new byte[4096]; 301 302 while (-1 != (read = plaintextInputStream.read(buffer))) { 303 multiCipherOutputStream.write(buffer, 0, read); 304 } 305 306 plaintextInputStream.close(); 307 multiCipherOutputStream.close(); 308 } 309 catch (IOException e) { 310 throw new CipherException(e); 311 } 312 } 313 314 public static byte[] encrypt(InputStream plaintextInputStream, List<CipherSpec> cipherSuites, SaltedSecretKey masterKey) throws CipherException { 315 ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream(); 316 encrypt(plaintextInputStream, ciphertextOutputStream, cipherSuites, masterKey); 317 318 return ciphertextOutputStream.toByteArray(); 319 } 320 321 public static byte[] decrypt(InputStream fromInputStream, SaltedSecretKey masterKey) throws CipherException { 322 try { 323 CipherSession cipherSession = new CipherSession(masterKey); 324 MultiCipherInputStream multiCipherInputStream = new MultiCipherInputStream(fromInputStream, cipherSession); 325 ByteArrayOutputStream plaintextOutputStream = new ByteArrayOutputStream(); 326 327 int read = -1; 328 byte[] buffer = new byte[4096]; 329 330 while (-1 != (read = multiCipherInputStream.read(buffer))) { 331 plaintextOutputStream.write(buffer, 0, read); 332 } 333 334 multiCipherInputStream.close(); 335 plaintextOutputStream.close(); 336 337 return plaintextOutputStream.toByteArray(); 338 } 339 catch (IOException e) { 340 throw new CipherException(e); 341 } 342 } 343 344 /** 345 * Generates a 2048-bit RSA key pair. 346 */ 347 public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException, CipherException, NoSuchProviderException { 348 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(CipherParams.CERTIFICATE_KEYPAIR_ALGORITHM, CipherParams.CRYPTO_PROVIDER_ID); 349 keyGen.initialize(CipherParams.CERTIFICATE_KEYPAIR_SIZE, secureRandom); 350 351 return keyGen.generateKeyPair(); 352 } 353 354 /** 355 * Generates a self-signed certificate, given a public/private key pair. 356 * 357 * @see <a href="https://code.google.com/p/gitblit/source/browse/src/com/gitblit/MakeCertificate.java?r=88598bb2f779b73479512d818c675dea8fa72138">Original source of this method</a> 358 */ 359 public static X509Certificate generateSelfSignedCertificate(String commonName, KeyPair keyPair) throws OperatorCreationException, 360 CertificateException, 361 InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException { 362 363 // Certificate CN, O and OU 364 X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE); 365 366 builder.addRDN(BCStyle.CN, commonName); 367 builder.addRDN(BCStyle.O, CipherParams.CERTIFICATE_ORGANIZATION); 368 builder.addRDN(BCStyle.OU, CipherParams.CERTIFICATE_ORGUNIT); 369 370 // Dates and serial 371 Date notBefore = new Date(System.currentTimeMillis() - 1 * 24 * 60 * 60 * 1000L); 372 Date notAfter = new Date(System.currentTimeMillis() + 5 * 365 * 24 * 60 * 60 * 1000L); 373 BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); 374 375 // Issuer and subject (identical, because self-signed) 376 X500Name issuer = builder.build(); 377 X500Name subject = issuer; 378 379 X509v3CertificateBuilder certificateGenerator = 380 new JcaX509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, keyPair.getPublic()); 381 382 ContentSigner signatureGenerator = new JcaContentSignerBuilder("SHA256WithRSAEncryption") 383 .setProvider(CipherParams.CRYPTO_PROVIDER) 384 .build(keyPair.getPrivate()); 385 386 X509Certificate certificate = new JcaX509CertificateConverter() 387 .setProvider(CipherParams.CRYPTO_PROVIDER) 388 .getCertificate(certificateGenerator.build(signatureGenerator)); 389 390 certificate.checkValidity(new Date()); 391 certificate.verify(certificate.getPublicKey()); 392 393 return certificate; 394 } 395 396 /** 397 * Creates an SSL context, given a key store and a trust store. 398 */ 399 public static SSLContext createSSLContext(KeyStore keyStore, KeyStore trustStore) throws Exception { 400 try { 401 // Server key and certificate 402 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 403 keyManagerFactory.init(keyStore, new char[0]); 404 405 KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); 406 407 // Trusted certificates 408 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 409 trustManagerFactory.init(trustStore); 410 411 TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); 412 413 // Create SSL context 414 SSLContext sslContext = SSLContext.getInstance("TLS"); 415 sslContext.init(keyManagers, trustManagers, null); 416 417 return sslContext; 418 } 419 catch (Exception e) { 420 throw new Exception("Unable to initialize SSL context", e); 421 } 422 } 423}