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();

以上代码在,最重要的部分:

  1. Builder(mAlias, KeyProperties.PURPOSE_WRAP_KEY) 声明这个是包装密钥的
  2. setAttestationChallenge(“12345678”.getBytes()) 需要生成验证证书。
  3. 证书的获取代码如下:
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();
    }
}

以上,完毕。
感谢浏览,如有不足,请大家指点一下,谢谢大家。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐