package org.nick.androidkeystore; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.Provider.Service; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.List; import java.util.Set; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.security.auth.x500.X500Principal; import org.spongycastle.asn1.ASN1Encoding; import org.spongycastle.asn1.DERObjectIdentifier; import org.spongycastle.asn1.x509.AlgorithmIdentifier; import org.spongycastle.asn1.x509.DigestInfo; import org.spongycastle.crypto.CryptoException; import org.spongycastle.crypto.DataLengthException; import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.InvalidCipherTextException; import org.spongycastle.crypto.digests.SHA512Digest; import org.spongycastle.crypto.encodings.OAEPEncoding; import org.spongycastle.crypto.params.RSAKeyParameters; import org.spongycastle.crypto.signers.PSSSigner; import android.annotation.SuppressLint; import android.content.Context; import android.security.KeyPairGeneratorSpec; import android.util.Log; public class Crypto { private static final String TAG = Crypto.class.getSimpleName(); private static String DELIMITER = "]"; private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; private static int KEY_LENGTH = 256; private static SecureRandom random = new SecureRandom(); private Crypto() { } @SuppressLint("DefaultLocale") public static void listAlgorithms(String algFilter) { Provider[] providers = Security.getProviders(); for (Provider p : providers) { String providerStr = String.format("%s/%s/%f\n", p.getName(), p.getInfo(), p.getVersion()); Log.d(TAG, providerStr); Set<Service> services = p.getServices(); List<String> algs = new ArrayList<String>(); for (Service s : services) { boolean match = true; if (algFilter != null) { match = s.getAlgorithm().toLowerCase() .contains(algFilter.toLowerCase()); } if (match) { String algStr = String.format("\t%s/%s/%s", s.getType(), s.getAlgorithm(), s.getClassName()); algs.add(algStr); } } Collections.sort(algs); for (String alg : algs) { Log.d(TAG, "\t" + alg); } Log.d(TAG, ""); } } public static SecretKey generateAesKey() { try { KeyGenerator kg = KeyGenerator.getInstance("AES"); kg.init(KEY_LENGTH); SecretKey key = kg.generateKey(); return key; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public static byte[] generateIv(int length) { byte[] b = new byte[length]; random.nextBytes(b); return b; } public static String encryptAesCbc(String plaintext, SecretKey key) { try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); byte[] iv = generateIv(cipher.getBlockSize()); Log.d(TAG, "IV: " + toHex(iv)); IvParameterSpec ivParams = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, key, ivParams); Log.d(TAG, "Cipher IV: " + (cipher.getIV() == null ? null : toHex(cipher.getIV()))); byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF-8")); return String.format("%s%s%s", toBase64(iv), DELIMITER, toBase64(cipherText)); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static String encryptRsaOaep(String plaintext, String keyAlias) { try { AndroidRsaEngine rsa = new AndroidRsaEngine(keyAlias, false); Digest digest = new SHA512Digest(); Digest mgf1digest = new SHA512Digest(); OAEPEncoding oaep = new OAEPEncoding(rsa, digest, mgf1digest, null); oaep.init(true, null); byte[] plainBytes = plaintext.getBytes("UTF-8"); byte[] cipherText = oaep.processBlock(plainBytes, 0, plainBytes.length); return toBase64(cipherText); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (InvalidCipherTextException e) { throw new RuntimeException(e); } } public static String decryptRsaOaep(String ciphertext, String keyAlias) { try { AndroidRsaEngine rsa = new AndroidRsaEngine(keyAlias, false); Digest digest = new SHA512Digest(); Digest mgf1digest = new SHA512Digest(); OAEPEncoding oaep = new OAEPEncoding(rsa, digest, mgf1digest, null); oaep.init(false, null); byte[] ciphertextBytes = fromBase64(ciphertext); byte[] plain = oaep.processBlock(ciphertextBytes, 0, ciphertextBytes.length); return new String(plain, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (InvalidCipherTextException e) { throw new RuntimeException(e); } } public static String toHex(byte[] bytes) { StringBuffer buff = new StringBuffer(); for (byte b : bytes) { buff.append(String.format("%02X", b)); } return buff.toString(); } public static String toBase64(byte[] bytes) { return Base64.encodeToString(bytes, Base64.NO_WRAP); } public static byte[] fromBase64(String base64) { return Base64.decode(base64, Base64.NO_WRAP); } public static String decryptAesCbc(String ciphertext, SecretKey key) { try { String[] fields = ciphertext.split(DELIMITER); if (fields.length != 2) { throw new IllegalArgumentException( "Invalid encypted text format"); } byte[] iv = fromBase64(fields[0]); byte[] cipherBytes = fromBase64(fields[1]); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec ivParams = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, key, ivParams); Log.d(TAG, "Cipher IV: " + toHex(cipher.getIV())); byte[] plaintext = cipher.doFinal(cipherBytes); String plainrStr = new String(plaintext, "UTF-8"); return plainrStr; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static RSAPublicKey createPublicKey(byte[] pubKeyBytes) throws GeneralSecurityException { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyBytes); KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(keySpec); return pubKey; } @SuppressWarnings("deprecation") public static byte[] createSha512EncryptionBlock(byte[] digest, int keySize) throws IOException, InvalidCipherTextException { // SHA512 DERObjectIdentifier oid = new DERObjectIdentifier( "2.16.840.1.101.3.4.2.3"); AlgorithmIdentifier sha512Aid = new AlgorithmIdentifier(oid); DigestInfo di = new DigestInfo(sha512Aid, digest); byte[] diDer = di.getEncoded(ASN1Encoding.DER); return padPkcs1(diDer, keySize); } // PKCS#1 padding public static byte[] padPkcs1(byte[] in, int keySize) { if (in.length > keySize) { throw new IllegalArgumentException("Data too long"); } byte[] result = new byte[keySize / 8]; result[0] = 0x0; result[1] = 0x01; // BT 1 // PS for (int i = 2; i != result.length - in.length - 1; i++) { result[i] = (byte) 0xff; } // end of padding result[result.length - in.length - 1] = 0x00; // D System.arraycopy(in, 0, result, result.length - in.length, in.length); return result; } @SuppressLint("NewApi") public static KeyPair generateRsaPairWithGenerator(Context ctx, String alais) throws Exception { Calendar notBefore = Calendar.getInstance(); Calendar notAfter = Calendar.getInstance(); notAfter.add(1, Calendar.YEAR); KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx) .setAlias(alais) .setSubject( new X500Principal(String.format("CN=%s, OU=%s", alais, ctx.getPackageName()))) .setSerialNumber(BigInteger.ONE) .setStartDate(notBefore.getTime()) .setEndDate(notAfter.getTime()).build(); KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); kpGenerator.initialize(spec); KeyPair kp = kpGenerator.generateKeyPair(); return kp; } @SuppressLint("NewApi") public static KeyPair generateEcPairWithGenerator(Context ctx, String alais) throws Exception { Calendar notBefore = Calendar.getInstance(); Calendar notAfter = Calendar.getInstance(); notAfter.add(1, Calendar.YEAR); KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx) .setAlias(alais) .setKeyType("EC") // curve .setKeySize(256) .setSubject( new X500Principal(String.format("CN=%s, OU=%s", alais, ctx.getPackageName()))) .setSerialNumber(BigInteger.ONE) .setStartDate(notBefore.getTime()) .setEndDate(notAfter.getTime()).build(); KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); kpGenerator.initialize(spec); KeyPair kp = kpGenerator.generateKeyPair(); return kp; } public static String signRsaPss(String keyAlias, String toSign) { try { RSAPublicKey pubKey = loadRsaPublicKey(keyAlias); AndroidRsaEngine rsa = new AndroidRsaEngine(keyAlias, true); Digest digest = new SHA512Digest(); Digest mgf1digest = new SHA512Digest(); PSSSigner signer = new PSSSigner(rsa, digest, mgf1digest, 512 / 8); RSAKeyParameters params = new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent()); signer.init(true, params); byte[] signedData = toSign.getBytes("UTF-8"); signer.update(signedData, 0, signedData.length); byte[] signature = signer.generateSignature(); return toBase64(signature); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (DataLengthException e) { throw new RuntimeException(e); } catch (CryptoException e) { throw new RuntimeException(e); } } public static RSAPublicKey loadRsaPublicKey(String keyAlias) throws GeneralSecurityException, IOException { return (RSAPublicKey) loadPublicKey(keyAlias); } public static PublicKey loadPublicKey(String keyAlias) throws GeneralSecurityException, IOException { java.security.KeyStore ks = java.security.KeyStore .getInstance("AndroidKeyStore"); ks.load(null); java.security.KeyStore.Entry keyEntry = ks.getEntry(keyAlias, null); return ((java.security.KeyStore.PrivateKeyEntry) keyEntry) .getCertificate().getPublicKey(); } public static PrivateKey loadPrivateKey(String keyAlias) throws GeneralSecurityException, IOException { java.security.KeyStore ks = java.security.KeyStore .getInstance("AndroidKeyStore"); ks.load(null); java.security.KeyStore.Entry keyEntry = ks.getEntry(keyAlias, null); return ((java.security.KeyStore.PrivateKeyEntry) keyEntry) .getPrivateKey(); } public static boolean verifyRsaPss(String signatureStr, String signedStr, String keyAlias) { try { RSAPublicKey pubKey = loadRsaPublicKey(keyAlias); AndroidRsaEngine rsa = new AndroidRsaEngine(keyAlias, true); Digest digest = new SHA512Digest(); Digest mgf1digest = new SHA512Digest(); PSSSigner signer = new PSSSigner(rsa, digest, mgf1digest, 512 / 8); RSAKeyParameters params = new RSAKeyParameters(false, pubKey.getModulus(), pubKey.getPublicExponent()); signer.init(false, params); byte[] signedData = signedStr.getBytes("UTF-8"); signer.update(signedData, 0, signedData.length); byte[] signature = fromBase64(signatureStr); boolean result = signer.verifySignature(signature); return result; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } public static String signEc(String keyAlias, String toSign) { try { PrivateKey privKey = loadPrivateKey(keyAlias); Signature sig = Signature.getInstance("SHA512WITHECDSA"); sig.initSign(privKey); byte[] signedData = toSign.getBytes("UTF-8"); sig.update(signedData); byte[] signature = sig.sign(); return toBase64(signature); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (DataLengthException e) { throw new RuntimeException(e); } } public static boolean verifyEc(String signatureStr, String signedStr, String keyAlias) { try { PublicKey pubKey = loadPublicKey(keyAlias); Signature sig = Signature.getInstance("SHA512WITHECDSA"); sig.initVerify(pubKey); byte[] signedData = signedStr.getBytes("UTF-8"); byte[] signature = fromBase64(signatureStr); sig.update(signedData); boolean result = sig.verify(signature); return result; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } }