问题描述
在 Android 早期的版本(Android N以前)中, 密钥生成过程类似如下的形式:
public KeyGenerator keyGen(byte[] strKey) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = null;
secureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
secureRandom.setSeed(strKey);
kgen.init(128);
return kgen;
} catch (Exception e) {
throw new RuntimeException("Error setting key. Cause: " + e);
}
}
后续版本(Android N 到 Android P 之间)中弃用了 SecureRandom.getInstance("SHA1PRNG", "Crypto")
这种形式的随机数生成, 改为如下的形式:
public final class CryptoProvider extends Provider {
/**
* Creates a Provider and puts parameters
*/
public CryptoProvider() {
super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
put("SecureRandom.SHA1PRNG",
"org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
public KeyGenerator keyGen(byte[] strKey) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = null;
secureRandom = SecureRandom.getInstance("SHA1PRNG", new CryptoProvider());
secureRandom.setSeed(strKey);
kgen.init(128);
return kgen;
} catch (Exception e) {
throw new RuntimeException("Error setting key. Cause: " + e);
}
}
但是在最新的 Android 版本中, 上述方法也会报 java.security.NoSuchProviderException
或 java.security.NoSuchAlgorithmException
异常, 下面是最新的写法.
最新解决方案
AES 算法类
//SymmetricEncryption.java
public class SymmetricEncryption {
public static final int KEY_SIZE = 32;
public byte[] keyGen(byte[] passwordBytes, int keySizeInBytes) {
//根据口令生成密钥
return InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(passwordBytes, keySizeInBytes);
}
public byte[] encrypt(byte[] plaintext, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyGen(key, KEY_SIZE), "AES"), ivParams);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(byte[] ciphertext, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] ivByte = new byte[cipher.getBlockSize()];
IvParameterSpec ivParamsSpec = new IvParameterSpec(ivByte);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyGen(key, KEY_SIZE), "AES"), ivParamsSpec);
return cipher.doFinal(ciphertext);
}
}
密钥生成
密钥生成(keyGen
)直接下载 Google 提供的密钥生成类: InsecureSHA1PRNGKeyDerivator.java
或者直接复制下面的代码(同样的代码, 只需要修改包名)
//InsecureSHA1PRNGKeyDerivator.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Stripped-down version of the SHA1PRNG provided by the Crypto provider.
* <p>
* The Crypto provider that offers this functionality was deprecated on Android.
* <p>
* Use this class only to retrieve encrypted data that couldn't be retrieved otherwise.
*/
class InsecureSHA1PRNGKeyDerivator {
/**
* Only public method. Derive a key from the given seed.
* <p>
* Use this method only to retrieve encrypted data that couldn't be retrieved otherwise.
*
* @param seed seed used for the random generator, usually coming from a password
* @param keySizeInBytes length of the array returned
*/
public static byte[] deriveInsecureKey(byte[] seed, int keySizeInBytes) {
InsecureSHA1PRNGKeyDerivator derivator = new InsecureSHA1PRNGKeyDerivator();
derivator.setSeed(seed);
byte[] key = new byte[keySizeInBytes];
derivator.nextBytes(key);
return key;
}
// constants to use in expressions operating on bytes in int and long variables:
// END_FLAGS - final bytes in words to append to message;
// see "ch.5.1 Padding the Message, FIPS 180-2"
// RIGHT1 - shifts to right for left half of long
// RIGHT2 - shifts to right for right half of long
// LEFT - shifts to left for bytes
// MASK - mask to select counter's bytes after shift to right
private static final int[] END_FLAGS = {
0x80000000, 0x800000, 0x8000, 0x80};
private static final int[] RIGHT1 = {
0, 40, 48, 56};
private static final int[] RIGHT2 = {
0, 8, 16,