Public 在 iOS 设备上生成的密钥在 Java 服务器上无效
Public key generated on iOS device invalid on Java server
我在 iOS 设备上生成的 SecKeyRef
有问题 - 当试图在 Java 服务器上使用它时,抛出异常:
InvalidKeyException: EC domain parameters must be encoded in the algorithm identifier
这是来自服务器代码的代码片段:
String key = ...
byte[] byteKey = Base64.decode(key.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(X509publicKey);
kf.generatePublic(X509publicKey);
抛出异常
密钥创建于 iOS,使用 SecKeyGeneratePair
[keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:256] forKey:(__bridge id)kSecAttrKeySizeInBits];
// Set the private key dictionary
[privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[privateKeyAttr setObject:self.privateTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set the public key dictionary
[publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[publicKeyAttr setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set attributes to top level dictionary
[keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs];
[keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs];
// Generate key pair
OSStatus sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);
密钥对创建成功。然后我使用以下代码
提取密钥的位数据
CFDataRef publicKeyBitsRef = NULL;
NSMutableDictionary *queryPublicKey = [NSMutableDictionary dictionary];
// Set the public key query dictionary.
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnData];
// Get the key bits.
OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBitsRef);
然后,我使用 CryptoExportImportManager
导出密钥
NSData *publicKeyIDERData = [manager exportPublicKeyToDER:keyBits keyType:(__bridge NSString*)kSecAttrKeyTypeEC keySize:256];
NSString *derKeyString = [publicKeyIDERData base64EncodedStringWithOptions:0];
根据 ,DER header 包含有关密钥类型和参数的信息,对于 secp256r1
密钥,它等同于以下数据
[
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
]
确实在导出时添加到密钥 header。
derKeyString
然后发送到后端并使用上面提到的 Java 代码进行处理。但是,抛出异常。
相同的后端还使用以下代码处理在 Android 设备上创建的密钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(
new ECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true).build());
keyPairGenerator.generateKeyPair();
Android 键工作得很好。
我做错了什么?在使用 SecKeyGeneratePair
创建密钥或导出 public 密钥时,我是否忘记了什么?
我解决了这个问题,虽然我不确定为什么。
我所做的是放弃 CryptoExportImportManager 库并手动创建密钥数据,如下所示:
unsigned char _encodedECOID[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
};
NSMutableData *data = [NSMutableData new];
[data appendBytes:_encodedECOID length:sizeof(_encodedECOID)];
[data appendData:keyBits]; // keyBits is od NSData type
现在 Java 服务器正确地从我的字符串(从 data
编码的 base64)创建 public 密钥。
然而,在查看了 CryptoExportImportManager 的源代码后,它从我的密钥位创建编码字符串的方式看起来像这样(在 Swift 中):
let curveOIDHeader: [UInt8] = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00]
let curveOIDHeaderLen: Int = 26
var data = Data(bytes: curveOIDHeader, count: curveOIDHeaderLen)
data.append(rawPublicKeyBytes)
它基本上做了完全相同的事情。那么区别在哪里呢?
现在唯一想到的是 header 存储方式的不同 - 在我的例子中它是一个 unsigned char
的数组,在图书馆的例子中它是一个 [=14] 的数组=].
根据this answer,C
类型unsigned char
和uint8_t
不等价,它们只保证具有相同的长度,但可以不同,即字节订购。
尽管该问题与 Swift 的 UInt8
无关(但被标记为 C
,其中 Objective-C 是超集),Swift 的 UInt8 type 没有说明它与 unsigned char
类型的关系,那将是我能看到的唯一合理的解释。
我在 iOS 设备上生成的 SecKeyRef
有问题 - 当试图在 Java 服务器上使用它时,抛出异常:
InvalidKeyException: EC domain parameters must be encoded in the algorithm identifier
这是来自服务器代码的代码片段:
String key = ...
byte[] byteKey = Base64.decode(key.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(X509publicKey);
kf.generatePublic(X509publicKey);
密钥创建于 iOS,使用 SecKeyGeneratePair
[keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:256] forKey:(__bridge id)kSecAttrKeySizeInBits];
// Set the private key dictionary
[privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[privateKeyAttr setObject:self.privateTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set the public key dictionary
[publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[publicKeyAttr setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set attributes to top level dictionary
[keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs];
[keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs];
// Generate key pair
OSStatus sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);
密钥对创建成功。然后我使用以下代码
提取密钥的位数据CFDataRef publicKeyBitsRef = NULL;
NSMutableDictionary *queryPublicKey = [NSMutableDictionary dictionary];
// Set the public key query dictionary.
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnData];
// Get the key bits.
OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBitsRef);
然后,我使用 CryptoExportImportManager
导出密钥NSData *publicKeyIDERData = [manager exportPublicKeyToDER:keyBits keyType:(__bridge NSString*)kSecAttrKeyTypeEC keySize:256];
NSString *derKeyString = [publicKeyIDERData base64EncodedStringWithOptions:0];
根据 secp256r1
密钥,它等同于以下数据
[
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
]
确实在导出时添加到密钥 header。
derKeyString
然后发送到后端并使用上面提到的 Java 代码进行处理。但是,抛出异常。
相同的后端还使用以下代码处理在 Android 设备上创建的密钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(
new ECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true).build());
keyPairGenerator.generateKeyPair();
Android 键工作得很好。
我做错了什么?在使用 SecKeyGeneratePair
创建密钥或导出 public 密钥时,我是否忘记了什么?
我解决了这个问题,虽然我不确定为什么。
我所做的是放弃 CryptoExportImportManager 库并手动创建密钥数据,如下所示:
unsigned char _encodedECOID[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
};
NSMutableData *data = [NSMutableData new];
[data appendBytes:_encodedECOID length:sizeof(_encodedECOID)];
[data appendData:keyBits]; // keyBits is od NSData type
现在 Java 服务器正确地从我的字符串(从 data
编码的 base64)创建 public 密钥。
然而,在查看了 CryptoExportImportManager 的源代码后,它从我的密钥位创建编码字符串的方式看起来像这样(在 Swift 中):
let curveOIDHeader: [UInt8] = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00]
let curveOIDHeaderLen: Int = 26
var data = Data(bytes: curveOIDHeader, count: curveOIDHeaderLen)
data.append(rawPublicKeyBytes)
它基本上做了完全相同的事情。那么区别在哪里呢?
现在唯一想到的是 header 存储方式的不同 - 在我的例子中它是一个 unsigned char
的数组,在图书馆的例子中它是一个 [=14] 的数组=].
根据this answer,C
类型unsigned char
和uint8_t
不等价,它们只保证具有相同的长度,但可以不同,即字节订购。
尽管该问题与 Swift 的 UInt8
无关(但被标记为 C
,其中 Objective-C 是超集),Swift 的 UInt8 type 没有说明它与 unsigned char
类型的关系,那将是我能看到的唯一合理的解释。