Android 导入WrappedKey
本文给出WrappedKey导入AES密钥的方案。本文参考代码 CTS中的导入测试。
WrappedKey 需要的格式:
KeyDescription ::= SEQUENCE(
keyFormat INTEGER, # Values from KeyFormat enum.
keyParams AuthorizationList,
)
SecureKeyWrapper ::= SEQUENCE(
version INTEGER, # Contains value 0
encryptedTransportKey OCTET_STRING,
initializationVector OCTET_STRING,
keyDescription KeyDescription,
encryptedKey OCTET_STRING,
tag OCTET_STRING
)
导入步骤
终端步骤
生成RSA 密钥对。如果可以,这里最好加入Android支持的密钥验证功能。认证工具为此提供了一个证书链,您可以用它验证该密钥对的属性。
如果设备支持硬件级密钥认证,将使用认证根密钥为此证书链中的根证书签名,设备制造商已在出厂时将根密钥注入到设备的硬件支持的密钥库中。
如果过了GMS的机器最好。如果可以最好能有ID认证,这样至少可以保证是机器生成的。可以与外部上传的ID做比对。
这部分代码很简单。
spec = new KeyGenParameterSpec.Builder(mAlias, KeyProperties.PURPOSE_WRAP_KEY)
.setKeySize(DEFAULT_KEY_SIZE)
.setUserAuthenticationRequired(false)
.setAttestationChallenge("12345678".getBytes())
.setCertificateSubject(new X500Principal("CN=" + mAlias))
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.build();
以上代码在,最重要的部分:
- Builder(mAlias, KeyProperties.PURPOSE_WRAP_KEY) 声明这个是包装密钥的
- setAttestationChallenge(“12345678”.getBytes()) 需要生成验证证书。
- 证书的获取代码如下:
Certificate[] certs = mKeyStore.getCertificateChain(AndroidKeyStoreRSAUtils.SAMPLE_ALIAS);
这里的验证规则,参考谷歌的密钥验证示例
云端步骤
记录密钥
云端收到上传的公钥,记录为rsaPublicKey。如果可以,验证密钥的证书和ID。
生成临时密钥
这步操作是生成保护密钥,并对保护密钥做保护。
服务器生成AES256的密钥,就是32个字节的密钥。记录为transKey。
后续操作部分有点疑问,文档上说的是需要XOR mask,但是CTS测试里面没有这一步。
先假设需要,然后真机验证,如果没法全部验证,就给出两种吧。t
- ransKey XOR mask。 mask 不在上面的格式里面,源码里面是:byte[] maskingKey = new byte[32];
- 然后使用 rsaPublicKey 使用OAEPParameterSpec(“SHA-256”, “MGF1”, MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);加密。PSpecified DEFAULT = new PSpecified(new byte[0]);必须这样,因为android keystore只支持这样。这个东西就是encryptedTransportKey.
byte[] aesKeyBytes = new byte[32];
random.nextBytes(aesKeyBytes);
// Encrypt ephemeral keys
OAEPParameterSpec spec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
Cipher pkCipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
pkCipher.init(Cipher.ENCRYPT_MODE, publicKey, spec);
byte[] encryptedEphemeralKeys = pkCipher.doFinal(aesKeyBytes);
真实密钥
1.真实需要导入的密钥,记录为 realKey.
2.使用上面的密钥 transKey 加密realKey,模式为:AES/GCM/NoPadding。
这里需要说明:
- gcm iv长度为12个字节.Android限定的;
- gcm 需要有个add的字节数组,这部分取KeyDescription
- 需要分别取出 gcm模式下的tag和完整密文。 gcm的tag ,在Java里是直接跟着密文后面的长度是:128/8=16
以上
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKeyBytes, "AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
byte[] aad = wrappedKeyDescription.getEncoded();
cipher.updateAAD(aad);
byte[] encryptedSecureKey = cipher.doFinal(keyMaterial);
int len = encryptedSecureKey.length;
int tagSize = (GCM_TAG_SIZE / 8);
byte[] tag = Arrays.copyOfRange(encryptedSecureKey, len - tagSize, len);
// Remove GCM tag from end of output
encryptedKey = Arrays.copyOfRange(encryptedSecureKey, 0, len - tagSize);
所以这里生成了三个部分:initializationVector,encryptedKey,和tag。
生成KeyDescription
上面说到的需要KeyDescription。这部分比较麻烦,我放到最后说。
这部分需要使用ASN.1的编码。我不确定maven的引用,参考使用:implementation group: ‘org.bouncycastle’, name: ‘bcpkix-jdk15to18’, version: ‘1.68’
这部分的代码有个好就是,在Android的源码中可以直接搞出来,路径是:源码根目录/external/bouncycastle。
以我们生成aes为例。
必须需要字段:algorithm 算法,类似是integer;Purposes 密钥用途,类似是set;keySize 密钥长度,类似是integer;BlockModes aes和3ds需要的,类型是set;Padding 填充方式,类型是set;noAuthRequired 是否需要授权,导入最好是true。
DEREncodableVector allPurposes = new DEREncodableVector();
allPurposes.add(new DERInteger(KM_PURPOSE_ENCRYPT));
allPurposes.add(new DERInteger(KM_PURPOSE_DECRYPT));
DERSet purposeSet = new DERSet(allPurposes);
DERTaggedObject purpose = new DERTaggedObject(true, 1, purposeSet);
DERTaggedObject algorithm = new DERTaggedObject(true, 2, new DERInteger(algorithm_));
DERTaggedObject keySize =
new DERTaggedObject(true, 3, new DERInteger(size));
DEREncodableVector allBlockModes = new DEREncodableVector();
allBlockModes.add(new DERInteger(KM_MODE_ECB));
allBlockModes.add(new DERInteger(KM_MODE_CBC));
DERSet blockModeSet = new DERSet(allBlockModes);
DERTaggedObject blockMode = new DERTaggedObject(true, 4, blockModeSet);
DEREncodableVector allPaddings = new DEREncodableVector();
allPaddings.add(new DERInteger(KM_PAD_PKCS7));
allPaddings.add(new DERInteger(KM_PAD_NONE));
DERSet paddingSet = new DERSet(allPaddings);
DERTaggedObject padding = new DERTaggedObject(true, 6, paddingSet);
DERTaggedObject noAuthRequired = new DERTaggedObject(true, 503, DERNull.INSTANCE);
// Build sequence
DEREncodableVector allItems = new DEREncodableVector();
allItems.add(purpose);
allItems.add(algorithm);
allItems.add(keySize);
allItems.add(blockMode);
allItems.add(padding);
allItems.add(noAuthRequired);
return new DERSequence(allItems);
以上,简单的就生成了。这就是keyParams。
最后,就差一个keyFormat。这个integer的。android有枚举的:KM_KEY_FORMAT_X509 = 0;KM_KEY_FORMAT_PKCS8 = 1;KM_KEY_FORMAT_RAW = 3; 我们是aes,所以选择3.
参考文件:
KeymasterDefs,这里列举了asn中使用的tag,和生成上述信息需要使用到的枚举。这个类就是keymaster_defs.h的Java版本。
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed 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.
*/
import java.util.HashMap;
import java.util.Map;
/**
* Class tracking all the keymaster enum values needed for the binder API to keystore.
* This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h
* See keymaster_defs.h for detailed descriptions of each constant.
* @hide
*/
public final class KeymasterDefs {
private KeymasterDefs() {}
// Tag types.
public static final int KM_INVALID = 0 << 28;
public static final int KM_ENUM = 1 << 28;
public static final int KM_ENUM_REP = 2 << 28;
public static final int KM_UINT = 3 << 28;
public static final int KM_UINT_REP = 4 << 28;
public static final int KM_ULONG = 5 << 28;
public static final int KM_DATE = 6 << 28;
public static final int KM_BOOL = 7 << 28;
public static final int KM_BIGNUM = 8 << 28;
public static final int KM_BYTES = 9 << 28;
public static final int KM_ULONG_REP = 10 << 28;
// Tag values.
public static final int KM_TAG_INVALID = KM_INVALID | 0;
public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
public static final int KM_TAG_KEY_SIZE = KM_UINT | 3;
public static final int KM_TAG_BLOCK_MODE = KM_ENUM_REP | 4;
public static final int KM_TAG_DIGEST = KM_ENUM_REP | 5;
public static final int KM_TAG_PADDING = KM_ENUM_REP | 6;
public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 7;
public static final int KM_TAG_MIN_MAC_LENGTH = KM_UINT | 8;
public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_UINT | 403;
public static final int KM_TAG_MAX_USES_PER_BOOT = KM_UINT | 404;
public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
public static final int KM_TAG_USER_ID = KM_UINT | 501;
public static final int KM_TAG_USER_SECURE_ID = KM_ULONG_REP | 502;
public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 503;
public static final int KM_TAG_USER_AUTH_TYPE = KM_ENUM | 504;
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507;
public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508;
public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509;
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
public static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = KM_BYTES | 716;
public static final int KM_TAG_ATTESTATION_ID_MODEL = KM_BYTES | 717;
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = KM_BOOL | 720;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
public static final int KM_TAG_AUTH_TOKEN = KM_BYTES | 1002;
public static final int KM_TAG_MAC_LENGTH = KM_UINT | 1003;
// Algorithm values.
public static final int KM_ALGORITHM_RSA = 1;
public static final int KM_ALGORITHM_EC = 3;
public static final int KM_ALGORITHM_AES = 32;
public static final int KM_ALGORITHM_3DES = 33;
public static final int KM_ALGORITHM_HMAC = 128;
// Block modes.
public static final int KM_MODE_ECB = 1;
public static final int KM_MODE_CBC = 2;
public static final int KM_MODE_CTR = 3;
public static final int KM_MODE_GCM = 32;
// Padding modes.
public static final int KM_PAD_NONE = 1;
public static final int KM_PAD_RSA_OAEP = 2;
public static final int KM_PAD_RSA_PSS = 3;
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
public static final int KM_PAD_PKCS7 = 64;
// Digest modes.
public static final int KM_DIGEST_NONE = 0;
public static final int KM_DIGEST_MD5 = 1;
public static final int KM_DIGEST_SHA1 = 2;
public static final int KM_DIGEST_SHA_2_224 = 3;
public static final int KM_DIGEST_SHA_2_256 = 4;
public static final int KM_DIGEST_SHA_2_384 = 5;
public static final int KM_DIGEST_SHA_2_512 = 6;
// Key origins.
public static final int KM_ORIGIN_GENERATED = 0;
public static final int KM_ORIGIN_IMPORTED = 2;
public static final int KM_ORIGIN_UNKNOWN = 3;
public static final int KM_ORIGIN_SECURELY_IMPORTED = 4;
// Key usability requirements.
public static final int KM_BLOB_STANDALONE = 0;
public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
// Operation Purposes.
public static final int KM_PURPOSE_ENCRYPT = 0;
public static final int KM_PURPOSE_DECRYPT = 1;
public static final int KM_PURPOSE_SIGN = 2;
public static final int KM_PURPOSE_VERIFY = 3;
public static final int KM_PURPOSE_WRAP = 5;
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
public static final int KM_KEY_FORMAT_PKCS8 = 1;
public static final int KM_KEY_FORMAT_RAW = 3;
// User authenticators.
public static final int HW_AUTH_PASSWORD = 1 << 0;
public static final int HW_AUTH_BIOMETRIC = 1 << 1;
// Error codes.
public static final int KM_ERROR_OK = 0;
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
public static final int KM_ERROR_INVALID_USER_ID = -15;
public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
public static final int KM_ERROR_KEY_EXPIRED = -25;
public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
public static final int KM_ERROR_VERIFICATION_FAILED = -30;
public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
public static final int KM_ERROR_INVALID_ARGUMENT = -38;
public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
public static final int KM_ERROR_INVALID_TAG = -40;
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
public static final int KM_ERROR_INVALID_RESCOPING = -42;
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
public static final int KM_ERROR_SECURE_HW_BUSY = -48;
public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
public static final int KM_ERROR_MISSING_NONCE = -51;
public static final int KM_ERROR_INVALID_NONCE = -52;
public static final int KM_ERROR_MISSING_MAC_LENGTH = -53;
public static final int KM_ERROR_KEY_RATE_LIMIT_EXCEEDED = -54;
public static final int KM_ERROR_CALLER_NONCE_PROHIBITED = -55;
public static final int KM_ERROR_KEY_MAX_OPS_EXCEEDED = -56;
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
public static final int KM_ERROR_DEVICE_LOCKED = -72;
public static final int KM_ERROR_UNIMPLEMENTED = -100;
public static final int KM_ERROR_VERSION_MISMATCH = -101;
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
static {
sErrorCodeToString.put(KM_ERROR_OK, "OK");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
"Unsupported MAC or authentication tag length");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
"Invalid user authorization timeout");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing");
sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV");
sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED,
"Caller-provided IV not permitted");
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
"Invalid MAC or authentication tag length");
sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked");
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
}
public static int getTagType(int tag) {
return tag & (0xF << 28);
}
public static String getErrorMessage(int errorCode) {
String result = sErrorCodeToString.get(errorCode);
if (result != null) {
return result;
}
return String.valueOf(errorCode);
}
}
大家可能注意到, public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;但是我们使用的是直接用1?为什么呢?
正式使用的使用,Android 会把高四位去掉,高四位主要是做类型识别。等我找到那部分的代码,我把他补充进来给大家看看。
ImportWrappedKeyTest 导入示例
import android.content.Context;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.SecureKeyImportUnavailableException;
import android.security.keystore.StrongBoxUnavailableException;
import android.security.keystore.WrappedKeyEntry;
import androidx.annotation.RequiresApi;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.DEREncodableVector;
import org.bouncycastle.asn1.DERInteger;
import static com.example.myapplication.KeymasterDefs.KM_ALGORITHM_3DES;
import static com.example.myapplication.KeymasterDefs.KM_ALGORITHM_AES;
import static com.example.myapplication.KeymasterDefs.KM_KEY_FORMAT_RAW;
import static com.example.myapplication.KeymasterDefs.KM_MODE_CBC;
import static com.example.myapplication.KeymasterDefs.KM_MODE_ECB;
import static com.example.myapplication.KeymasterDefs.KM_PAD_NONE;
import static com.example.myapplication.KeymasterDefs.KM_PAD_PKCS7;
import static com.example.myapplication.KeymasterDefs.KM_PURPOSE_DECRYPT;
import static com.example.myapplication.KeymasterDefs.KM_PURPOSE_ENCRYPT;
public class ImportWrappedKeyTest {
private Context context;
private static final String TAG = "ImportWrappedKeyTest";
private static final String ALIAS = "my key";
private static final String WRAPPING_KEY_ALIAS = "my_favorite_wrapping_key";
private static final int WRAPPED_FORMAT_VERSION = 0;
private static final int GCM_TAG_SIZE = 128;
SecureRandom random = new SecureRandom();
public Context getContext() {
return context;
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void testKeyStore_ImportWrappedKey() throws Exception {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
KeyPair kp;
try {
kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
} catch (SecureKeyImportUnavailableException e) {
return;
}
try {
importWrappedKey(wrapKey(
kp.getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
} catch (SecureKeyImportUnavailableException e) {
return;
}
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("AES/ECB/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("AES/ECB/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void testKeyStore_ImportWrappedKeyWrappingKeyMissing() throws Exception {
final String EXPECTED_FAILURE = "Failed to import wrapped key. Keystore error code: 7";
String failureMessage = null;
try {
byte [] fakeWrappedKey = new byte[1];
importWrappedKey(fakeWrappedKey, WRAPPING_KEY_ALIAS + "_Missing");
} catch (KeyStoreException e) {
failureMessage = e.getMessage();
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void testKeyStore_ImportWrappedKey_3DES() throws Exception {
/*if (!TestUtils.java.supports3DES()) {
return;
}*/
KeyGenerator kg = KeyGenerator.getInstance("DESEDE");
kg.init(168);
byte[] keyMaterial = kg.generateKey().getEncoded();
random.nextBytes(keyMaterial);
byte[] mask = new byte[24]; // Zero mask
KeyPair kp;
try {
kp = genKeyPair(WRAPPING_KEY_ALIAS, false);
} catch (SecureKeyImportUnavailableException e) {
return;
}
try {
importWrappedKey(wrapKey(
kp.getPublic(),
keyMaterial,
mask,
makeAuthList(168, KM_ALGORITHM_3DES)));
} catch (SecureKeyImportUnavailableException e) {
return;
}
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void testKeyStore_ImportWrappedKey_3DES_StrongBox() throws Exception {
/*if (!TestUtils.supports3DES()) {
return;
}*/
/*if (TestUtils.hasStrongBox(getContext())) {
KeyGenerator kg = KeyGenerator.getInstance("DESEDE");
kg.init(168);
byte[] keyMaterial = kg.generateKey().getEncoded();
random.nextBytes(keyMaterial);
byte[] mask = new byte[24]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(168, KM_ALGORITHM_3DES)));
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("DESede/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
} else {
try {
genKeyPair(WRAPPING_KEY_ALIAS, true);
} catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
}
}*/
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void testKeyStore_ImportWrappedKey_AES_StrongBox() throws Exception {
/*if (TestUtils.hasStrongBox(getContext())) {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
// Use Key
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
Key key = keyStore.getKey(ALIAS, null);
Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.ENCRYPT_MODE, key);
IvParameterSpec paramSpec = new IvParameterSpec(c.getIV());
byte[] encrypted = c.doFinal("hello, world".getBytes());
c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, paramSpec);
} else*/ {
try {
random.setSeed(0);
byte[] keyMaterial = new byte[32];
random.nextBytes(keyMaterial);
byte[] mask = new byte[32]; // Zero mask
importWrappedKey(wrapKey(
genKeyPair(WRAPPING_KEY_ALIAS, true).getPublic(),
keyMaterial,
mask,
makeAuthList(keyMaterial.length * 8, KM_ALGORITHM_AES)));
} catch (StrongBoxUnavailableException | SecureKeyImportUnavailableException e) {
}
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void importWrappedKey(byte[] wrappedKey, String wrappingKeyAlias) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null, null);
AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(wrappingKeyAlias,
KeyProperties.PURPOSE_WRAP_KEY)
.setDigests(KeyProperties.DIGEST_SHA256)
.build();
Entry wrappedKeyEntry = new WrappedKeyEntry(wrappedKey, wrappingKeyAlias,
"RSA/ECB/OAEPPadding", spec);
keyStore.setEntry(ALIAS, wrappedKeyEntry, null);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public void importWrappedKey(byte[] wrappedKey) throws Exception {
importWrappedKey(wrappedKey, WRAPPING_KEY_ALIAS);
}
public byte[] wrapKey(PublicKey publicKey, byte[] keyMaterial, byte[] mask,
DERSequence authorizationList)
throws Exception {
// Build description
DEREncodableVector descriptionItems = new DEREncodableVector();
descriptionItems.add(new DERInteger(KM_KEY_FORMAT_RAW));
descriptionItems.add(authorizationList);
DERSequence wrappedKeyDescription = new DERSequence(descriptionItems);
// Generate 12 byte initialization vector
byte[] iv = new byte[12];
random.nextBytes(iv);
// Generate 256 bit AES key. This is the ephemeral key used to encrypt the secure key.
byte[] aesKeyBytes = new byte[32];
random.nextBytes(aesKeyBytes);
// Encrypt ephemeral keys
OAEPParameterSpec spec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
Cipher pkCipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
pkCipher.init(Cipher.ENCRYPT_MODE, publicKey, spec);
byte[] encryptedEphemeralKeys = pkCipher.doFinal(aesKeyBytes);
// Encrypt secure key
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKeyBytes, "AES");
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
byte[] aad = wrappedKeyDescription.getEncoded();
cipher.updateAAD(aad);
byte[] encryptedSecureKey = cipher.doFinal(keyMaterial);
// Get GCM tag. Java puts the tag at the end of the ciphertext data :(
int len = encryptedSecureKey.length;
int tagSize = (GCM_TAG_SIZE / 8);
byte[] tag = Arrays.copyOfRange(encryptedSecureKey, len - tagSize, len);
// Remove GCM tag from end of output
encryptedSecureKey = Arrays.copyOfRange(encryptedSecureKey, 0, len - tagSize);
// Build ASN.1 DER encoded sequence WrappedKeyWrapper
DEREncodableVector items = new DEREncodableVector();
items.add(new DERInteger(WRAPPED_FORMAT_VERSION));
items.add(new DEROctetString(encryptedEphemeralKeys));
items.add(new DEROctetString(iv));
items.add(wrappedKeyDescription);
items.add(new DEROctetString(encryptedSecureKey));
items.add(new DEROctetString(tag));
return new DERSequence(items).getEncoded(ASN1Encoding.DER);
}
/**
* xor of two byte[] for masking or unmasking transit keys
*/
private byte[] xor(byte[] key, byte[] mask) {
byte[] out = new byte[key.length];
for (int i = 0; i < key.length; i++) {
out[i] = (byte) (key[i] ^ mask[i]);
}
return out;
}
private DERSequence makeAuthList(int size,
int algorithm_) {
// Make an AuthorizationList to describe the secure key
// https://developer.android.com/training/articles/security-key-attestation.html#verifying
DEREncodableVector allPurposes = new DEREncodableVector();
allPurposes.add(new DERInteger(KM_PURPOSE_ENCRYPT));
allPurposes.add(new DERInteger(KM_PURPOSE_DECRYPT));
DERSet purposeSet = new DERSet(allPurposes);
DERTaggedObject purpose = new DERTaggedObject(true, 1, purposeSet);
DERTaggedObject algorithm = new DERTaggedObject(true, 2, new DERInteger(algorithm_));
DERTaggedObject keySize =
new DERTaggedObject(true, 3, new DERInteger(size));
DEREncodableVector allBlockModes = new DEREncodableVector();
allBlockModes.add(new DERInteger(KM_MODE_ECB));
allBlockModes.add(new DERInteger(KM_MODE_CBC));
DERSet blockModeSet = new DERSet(allBlockModes);
DERTaggedObject blockMode = new DERTaggedObject(true, 4, blockModeSet);
DEREncodableVector allPaddings = new DEREncodableVector();
allPaddings.add(new DERInteger(KM_PAD_PKCS7));
allPaddings.add(new DERInteger(KM_PAD_NONE));
DERSet paddingSet = new DERSet(allPaddings);
DERTaggedObject padding = new DERTaggedObject(true, 6, paddingSet);
DERTaggedObject noAuthRequired = new DERTaggedObject(true, 503, DERNull.INSTANCE);
// Build sequence
DEREncodableVector allItems = new DEREncodableVector();
allItems.add(purpose);
allItems.add(algorithm);
allItems.add(keySize);
allItems.add(blockMode);
allItems.add(padding);
allItems.add(noAuthRequired);
return new DERSequence(allItems);
}
@RequiresApi(api = Build.VERSION_CODES.P)
private KeyPair genKeyPair(String alias, boolean isStrongBoxBacked) throws Exception {
KeyPairGenerator kpg =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
kpg.initialize(
new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_WRAP_KEY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setIsStrongBoxBacked(isStrongBoxBacked)
.build());
return kpg.generateKeyPair();
}
}
以上,完毕。
感谢浏览,如有不足,请大家指点一下,谢谢大家。