Java tutorial
/* * Copyright (C) 2013 Joshua Michael Hertlein <jmhertlein@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 net.jmhertlein.core.crypto; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; /** * * @author joshua */ public abstract class Keys { /** * Saves the given key to the given file. This method will NOT clobber * existing files- will return false if file exists The file will be * created, along with any parent directories needed. * * @param file name of file to save to * @param key key to save * * @return true if successfully written, false otherwise */ public static boolean storeKey(String file, Key key) { File f = new File(file); if (!f.exists()) try { if (f.getParentFile() != null) f.getParentFile().mkdirs(); f.createNewFile(); } catch (IOException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); } else return false; try (FileOutputStream fos = new FileOutputStream(file); PrintStream ps = new PrintStream(fos)) { ps.println(Base64.encodeBase64String(key.getEncoded())); return true; } catch (IOException ioe) { ioe.printStackTrace(); return false; } } /** * Saves the given key to the given file. This method will NOT clobber * existing files- will return false if file exists The file will be * created, along with any parent directories needed. * * @param f * @param key key to save * * @return true if successfully written, false otherwise */ public static boolean storeKey(File f, Key key) { return storeKey(f.getAbsolutePath(), key); } /** * Loads the Base64 encoded, PKCS8 formatted RSA private key from the file * * @param file * * @return */ public static PrivateKey loadPrivateKey(String file) { try { PKCS8EncodedKeySpec spec = getPKCS8KeySpec(file); if (spec == null) return null; return KeyFactory.getInstance("RSA").generatePrivate(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); } return null; } /** * Loads the Base64 encoded, X509 formatted RSA public key from the file * * @param file * * @return */ public static PublicKey loadPubKey(String file) { try { X509EncodedKeySpec spec = getX509KeySpec(file); if (spec == null) return null; return KeyFactory.getInstance("RSA").generatePublic(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); } return null; } /** * Loads the Base64 encoded, X509 formatted RSA public key from the file * * @param f * * @return */ public static PublicKey loadPubKey(File f) { return loadPubKey(f.getPath()); } /** * Loads the Base64 encoded, PKCS8 formatted RSA private key from the file * * @param f * * @return */ public static PrivateKey loadPrivateKey(File f) { return loadPrivateKey(f.getPath()); } private static X509EncodedKeySpec getX509KeySpec(String file) { byte[] decoded; try (Scanner scan = new Scanner(new File(file))) { String output = ""; while (scan.hasNextLine()) { output += scan.nextLine(); } decoded = Base64.decodeBase64(output); } catch (IOException ioe) { return null; } return new X509EncodedKeySpec(decoded); } private static PKCS8EncodedKeySpec getPKCS8KeySpec(String file) { byte[] decoded; try (Scanner scan = new Scanner(new File(file))) { String output = ""; while (scan.hasNextLine()) { output += scan.nextLine(); } decoded = Base64.decodeBase64(output); } catch (IOException ioe) { //ioe.printStackTrace(); return null; } return new PKCS8EncodedKeySpec(decoded); } /** * Generates a new RSA public/private key pair. * * The system's default SecureRandom is used * * @param bits the length of the keys * * @return the new key pair, or null if the RSA algorithm is not supported */ public static KeyPair newRSAKeyPair(int bits) { KeyPairGenerator keyPairGen; try { keyPairGen = KeyPairGenerator.getInstance("RSA"); } catch (NoSuchAlgorithmException ex) { return null; } keyPairGen.initialize(bits); return keyPairGen.generateKeyPair(); } /** * Generates a new Elliptic Curve Digital Signature Algorithm (ECDSA) public/private key pair. * * System's default SecureRandom is used * @param curveName the name of a pre-defined elliptic curve (e.g. secp521r1) * @param provider the JCE provider to use * @return a new ECDSA key pair */ public static KeyPair newECDSAKeyPair(String curveName, String provider) { KeyPair ret; try { ECGenParameterSpec ecGenSpec = new ECGenParameterSpec(curveName); KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", provider); g.initialize(ecGenSpec, new SecureRandom()); ret = g.generateKeyPair(); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); ret = null; } return ret; } /** * Generates a new Elliptic Curve Digital Signature Algorithm (ECDSA) public/private key pair. * * The system's default SecureRandom is used, and the secp521r1 named elliptic curve is used * * @param provider the JCE provider to use * @return a new ECDSA key pair */ public static KeyPair newECDSAKeyPair(String provider) { return newECDSAKeyPair("secp521r1", provider); } /** * Generates a new Elliptic Curve Digital Signature Algorithm (ECDSA) public/private key pair. * * The system's default SecureRandom is used, and the secp521r1 named elliptic curve is used * * The BouncyCastle provider is used if possible, otherwise the most-preferred installed JCE provider is used * * @return a new ECDSA key pair */ public static KeyPair newECDSAKeyPair() { if (Security.getProvider("BC") == null) return newECDSAKeyPair("secp521r1", Security.getProviders()[0].getName()); else return newECDSAKeyPair("secp521r1", "BC"); } /** * Generates a new AES key using the system's default SecureRandom * * @param bits * * @return the AES key, or null if the AES algorithm is not available on the system */ public static SecretKey newAESKey(int bits) { KeyGenerator keyGen; try { keyGen = KeyGenerator.getInstance("AES"); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); return null; } keyGen.init(bits); return keyGen.generateKey(); } /** * Given a Base64-encoded, X509-formatted RSA public key, returns a PublicKey object representing it * * @param encodedKey * * @return the RSA public key, or null if the RSA algorithm is not available on the system */ public static PublicKey getPublicKeyFromBASE64X509Encoded(String encodedKey) { byte[] decoded = Base64.decodeBase64(encodedKey); try { return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded)); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); return null; } } /** * Given a Base64-encoded, PKCS8-formatted RSA private key, returns a PrivateKey object representing it * * @param encodedKey * * @return the RSA private key, or null if the RSA algorithm is not available on the system */ public static PrivateKey getPrivateKeyFromBASE64PKCS8Encoded(String encodedKey) { byte[] decoded = Base64.decodeBase64(encodedKey); try { return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); return null; } } /** * Gets a Base64 representation of the given Key * * @param key * * @return */ public static String getBASE64ForKey(Key key) { return Base64.encodeBase64String(key.getEncoded()); } /** * Given an X509-formatted encoding of an RSA public key, returns the PublicKey object representing it * * @param bytes * * @return the RSA public key, or null if the RSA algorithm is not available on the system */ public static PublicKey getRSAPublicKeyFromEncoded(byte[] bytes) { try { return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bytes)); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { Logger.getLogger(Keys.class.getName()).log(Level.SEVERE, null, ex); return null; } } /** * Given the encoding of an AES secret key, returns the SecretKey object representing it * * @param bytes * * @return the AES SecretKey, or null if the AES algorithm is not available on the system */ public static SecretKey getAESSecretKeyFromEncoded(byte[] bytes) { return new SecretKeySpec(bytes, "AES"); } /** * Given a secret key and an output stream, wraps the output stream first in a CipherOutputStream using the given secret key, then in an ObjectOutputStream * * @param key the secret key to use to encrypt data with * @param os the output stream to encrypt and wrap * * @return an ObjectOutputStream whose data will be encrypted with the secret key * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException * @throws IOException */ public static ObjectOutputStream getEncryptedObjectOutputStream(SecretKey key, OutputStream os) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException { Cipher outCipher = Cipher.getInstance("AES/CFB8/NoPadding"); outCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getKeyBytes(key))); return new ObjectOutputStream(new CipherOutputStream(os, outCipher)); } /** * Given a secret key and an input stream, wraps the input stream first in a CipherInputStream using the given secret key, then in an ObjectInputStream * * @param key the secret key to use to encrypt data with * @param is the input stream to encrypt and wrap * * @return an ObjectInputStream whose data will be encrypted with the secret key * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException * @throws IOException */ public static ObjectInputStream getEncryptedObjectInputStream(SecretKey key, InputStream is) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IOException { Cipher inCipher = Cipher.getInstance("AES/CFB8/NoPadding"); inCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getKeyBytes(key))); return new ObjectInputStream(new CipherInputStream(is, inCipher)); } private static byte[] getKeyBytes(SecretKey k) { byte[] key = k.getEncoded(); byte[] keyBytes = new byte[16]; System.arraycopy(key, 0, keyBytes, 0, Math.min(key.length, keyBytes.length)); return keyBytes; } }