【问题标题】:Difference between PKCS1-padding/RSA encryption ios objc and javaPKCS1-padding/RSA 加密 ios objc 和 java 的区别
【发布时间】:2018-06-19 21:01:21
【问题描述】:

我正在为 ios 和 Android 开发一个应用程序。我对加密任务比较陌生,在过去的 3 天里,我一直在碰壁,因为我无法运行 RSA 加密。

两个客户端都从 Java 服务器接收公钥。在 android 中我(显然,因为它与服务器端的代码几乎相同)没有问题,但 ios 部分似乎根本不兼容。我想用公钥加密一小段数据(aes 密钥),这就是我在 Java 中这样做的方式:

try {
    String publickey  = "MCwwDQYJKoZIhvcNAQEBBQADGwAwGAIRAK+dBpbOKw+1VKMWoFxjU6UCAwEAAQ==";
    byte[] bArr = Crypto.base64Decode(publicKey, false);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
    EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey);
    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

    Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", "BC");
    cipher.init(1,publicKey);
    int cipherBlockSize = cipher.getBlockSize();
    ByteArrayOutputStream bArrOut = new ByteArrayOutputStream();
    bArrOut.flush();
    int pos = 0;
    Log.i("ContentBufferLength", contentBuffer.length+"");

    while (true) {
        if (cipherBlockSize > contentBuffer.length - pos) {
            cipherBlockSize = contentBuffer.length - pos;
        }
        Log.i("CipherBlockSize", cipherBlockSize+"");
        byte[] tmp = cipher.doFinal(contentBuffer, pos, cipherBlockSize);
        bArrOut.write(tmp);
        pos += cipherBlockSize;
        if (contentBuffer.length <= pos) {
            break;
        }
    }
    bArrOut.flush();
    encryptedBuffer = bArrOut.toByteArray();
    bArrOut.close();
} catch (Exception ex) {
    throw ex;
}

//  Log.i("Encrypted Buffer Length", encryptedBuffer.length+"");
return encryptedBuffer;

这是我的(不能正常工作的)ios代码,从这里借来的:

http://blog.wingsofhermes.org/?p=75 和苹果密码练习。

-(NSString* )encryptWithPublicKey:(NSString*)key input:(NSString*) input {
    const size_t BUFFER_SIZE =      16;
    const size_t CIPHER_BUFFER_SIZE = 16;
   //const uint32_t PADDING = kSecPaddingNone;
    const uint32_t PADDING = kSecPaddingPKCS1;

    static const UInt8 publicKeyIdentifier[] = "de.irgendwas.app";

    NSData *publicTag;

    publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];

    NSMutableDictionary *publicKey2 = [[NSMutableDictionary alloc] init];
    [publicKey2 setObject:kSecClassKey forKey:kSecClass];
    [publicKey2 setObject:kSecAttrKeyTypeRSA forKey:kSecAttrKeyType];
    [publicKey2 setObject:publicTag forKey:kSecAttrApplicationTag];
    SecItemDelete((CFDictionaryRef)publicKey2);


    NSData *strippedPublicKeyData = [NSData dataFromBase64String:key];

    unsigned char * bytes = (unsigned char *)[strippedPublicKeyData bytes];
    size_t bytesLen = [strippedPublicKeyData length];

    size_t i = 0;
    if (bytes[i++] != 0x30)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    /* Skip size bytes */
    if (bytes[i] > 0x80)
        i += bytes[i] - 0x80 + 1;
    else
        i++;

    if (i >= bytesLen)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    if (bytes[i] != 0x30)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    /* Skip OID */
    i += 15;

    if (i >= bytesLen - 2)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    if (bytes[i++] != 0x03)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    /* Skip length and null */
    if (bytes[i] > 0x80)
        i += bytes[i] - 0x80 + 1;
    else
        i++;

    if (i >= bytesLen)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    if (bytes[i++] != 0x00)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    if (i >= bytesLen)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    strippedPublicKeyData = [NSData dataWithBytes:&bytes[i] length:bytesLen - i];

    DLog(@"X.509 Formatted Public Key bytes:\n%@",[strippedPublicKeyData description]);

    if (strippedPublicKeyData == nil)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];



    CFTypeRef persistKey = nil;
    [publicKey2 setObject:strippedPublicKeyData forKey:kSecValueData];
    [publicKey2 setObject: (kSecAttrKeyClassPublic) forKey:kSecAttrKeyClass];
    [publicKey2 setObject:[NSNumber numberWithBool:YES] forKey:kSecReturnPersistentRef];

    OSStatus secStatus = SecItemAdd((CFDictionaryRef)publicKey2, &persistKey);

    if (persistKey != nil) CFRelease(persistKey);

    if ((secStatus != noErr) && (secStatus != errSecDuplicateItem))
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];

    SecKeyRef keyRef = nil;
    [publicKey2 removeObjectForKey:kSecValueData];
    [publicKey2 removeObjectForKey:kSecReturnPersistentRef];
    [publicKey2 setObject:[NSNumber numberWithBool:YES] forKey:kSecReturnRef];
    [publicKey2 setObject: kSecAttrKeyTypeRSA forKey:kSecAttrKeyType];

    SecItemCopyMatching((CFDictionaryRef)publicKey2,(CFTypeRef *)&keyRef);
    if (!keyRef)
    [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];  

    uint8_t *plainBuffer;
    uint8_t *cipherBuffer;
    uint8_t *decryptedBuffer;


    const char inputString[] = "1234";
    int len = strlen(inputString);
    // TODO: this is a hack since i know inputString length will be less than BUFFER_SIZE
    if (len > BUFFER_SIZE) len = BUFFER_SIZE-1;
    plainBuffer = (uint8_t *)calloc(BUFFER_SIZE, sizeof(uint8_t));
    cipherBuffer = (uint8_t *)calloc(CIPHER_BUFFER_SIZE, sizeof(uint8_t));
    decryptedBuffer = (uint8_t *)calloc(BUFFER_SIZE, sizeof(uint8_t));

    strncpy( (char *)plainBuffer, inputString, len);

    size_t plainBufferSize = strlen((char *)plainBuffer);
    size_t cipherBufferSize = CIPHER_BUFFER_SIZE;

    NSLog(@"SecKeyGetBlockSize() public = %lu", SecKeyGetBlockSize(keyRef));
    //  Error handling
    // Encrypt using the public.
    OSStatus status = noErr;

    status = SecKeyEncrypt(keyRef,
                           PADDING,
                           plainBuffer,
                           plainBufferSize,
                           &cipherBuffer[0],
                           &cipherBufferSize
                           );
    NSLog(@"encryption result code: %ld (size: %lu)", status, cipherBufferSize);

    return [[[NSString stringWithFormat:@"%s",cipherBuffer] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
}

出于测试目的和简单性,目前我尝试仅加密长度为 4 个字节的输入。这应该足够小以容纳一个块。公钥导入和加密过程似乎有效,但是与 android 方法相比,我总是收到更长的输出。

到目前为止,我遇到的唯一区别是 SecKeyGetBlockSize returns 16 和 java cipher.blocksize 返回 5。我认为其他 11 个字节是为 pkcs1 填充保留的,但是如何在 @987654325 中强制执行相同的行为@?

【问题讨论】:

  • 你能用更大的钥匙试试吗? 128 位的密钥有点小,一些加密库不适用于 512 位以下的任何内容(我应该尝试使用 1024 以确保)。为什么要尝试使用在现实世界中无用的密钥大小?
  • 感谢您的回答。我会建议它,但我不对服务器端代码负责。
  • 您找出问题所在了吗?我面临着类似的问题,任何帮助将不胜感激。
  • 你能告诉我,在从 java 服务器导入公钥时,密钥的格式意味着我正在获取公共的 .key 扩展文件(public.key)...正确的扩展来做加密

标签: java android ios encryption rsa


【解决方案1】:

尝试将密文拆分为多个部分,使每个部分包含 16 个字符长并分别对其进行解码。我也遇到了同样的问题,但这是在 PHP 中使用了很长时间,上面的技巧对我有用。

这可能会帮助您摆脱问题。

【讨论】:

    【解决方案2】:

    解码 Base64 密钥给出:

    MCwwDQYJKoZIhvcNAQEBBQADGwAwGAIRAK+dBpbOKw+1VKMWoFxjU6UCAwEAAQ==
    -> 302c300d06092a864886f70d0101010500031b003018021100af9d0696ce2b0fb554a316a05c6353a50203010001
    

    将其解释为 DER 编码的 ASN.1,我们发现:

    30(2c) //SEQUENCE
      30(0d)  //SEQUENCE
        06(09): 2a 86 48 86 f7 0d 01 01 01  //OID 1.2.840.113548.1.1.1 (RSA Encryption)
        05(00): //NULL                           
        03(1b): [00] 30 18 02 11 00 af 9d 06 96 ce 2b 0f b5 54 a3 16 a0 5c 63 53 a5 02 03 01 00 01 //BITSTRING
    

    BITSTRING 似乎也包含 DER 编码的 ASN.1:

    30(18) //SEQUENCE
      02(11): 00 af 9d 06 96 ce 2b 0f b5 54 a3 16 a0 5c 63 53 a5 02 03 01 00 01 //INTEGER
    
     = 0xaf9d0696ce2b0fb554a316a05c6353a50203010001
    

    遍历 IOS 代码,可以看到它正在解析 DER 编码的 ASN.1。它正确识别了前两个 SEQUENCE 标记,并跳过了 OID 字段,甚至没有验证它是否是 OID。那么问题就出现了:IOS代码期望下一个标签是 BITSTRING(0x03)——但在我们的数据中,我们有一个额外的 NULL(0x05) 字段来表示公共指数是隐式的。 IOS 代码在遇到 0x05 标记时引发异常。如果 NULL 不存在,我们看到 IOS 代码将成功提取 BITSTRING 的内容。

    所以:要么 NULL 是可选字段,并且 IOS 代码不允许它,要么 IOS 代码需要不同的 ASN.1 结构。例如,BITSTRING 似乎也是一个 DER 编码的 ASN.1 INTEGER(可能是 RSA 模数)。然而 IOS 代码并没有尝试解析它。可能是 IOS SecKeyEncrypt 例程需要这种模数格式,也可能是调用者应该提取模数的原始字节。

    所以仍然需要进行一些实验。但是,如果此代码要解析提供的数据对象,则肯定需要以下附加条件:

    /* Skip OID */
    i += 15;
    
    if (i >= bytesLen - 2)
        [Exception raise:FAILURE function:__PRETTY_FUNCTION__ line:__LINE__ description:@"Could not set public key."];
    
    if (bytes[i] == 0x05)    /* This should handle the spurious ASN.1 NULL field */
        i += 2;
    
    if (bytes[i++] != 0x03)
    

    【讨论】:

      【解决方案3】:

      在 Android 或 Java 中,生成的密钥采用标准 ASN.1 格式,在外部世界(客户端、服务器端)中运行良好,但在 iOS 中,生成的密钥(公共、私有)采用原始格式,您必须将其转换为适当的 ASN.1 格式以使其可用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-08
        • 1970-01-01
        • 2014-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多