简体   繁体   English

PKCS1-padding / RSA加密ios objc和java之间的区别

[英]Difference between PKCS1-padding/RSA encryption ios objc and java

I'm developing a application for ios and Android. 我正在为ios和Android开发应用程序。 I'm relatively new to crypto tasks and for the last 3 Days I keep banging my head against the wall because I'm not able to get RSA encryption running. 我对加密任务比较陌生,在过去的3天里,我不停地撞墙,因为我无法运行RSA加密。

Both clients receive a public key from a java server. 两个客户端都从Java服务器接收公钥。 In android i have (obviously, because it is almost the same code as on server side) no troubles, but the ios part seems to be not compatible at all. 在android我有(显然,因为它几乎与服务器端的代码相同)没有麻烦,但ios部分似乎根本不兼容。 I want to encrypt a little piece of data (aes key) with the public key and this is how I do this in Java: 我想用公钥加密一小段数据(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;

And this is my (not properly working) ios code, borrowed from here: 这是我的(不正常工作)ios代码,从这里借来的:

http://blog.wingsofhermes.org/?p=75 and the apple crypto exercises. 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];
}

For testing purposes and simplicity for the moment I am trying to encrypt only a input with a length of 4 bytes. 出于测试目的和简单性,我试图仅加密长度为4个字节的输入。 This should be small enough to fit one block. 这应该小到足以适合一个街区。 The public key import and the encrypting process seems to work, however I always receive a much longer output compared to the android method. 公钥导入和加密过程似乎有效,但与android方法相比,我总是收到更长的输出。

The only difference I encountered so far is the fact, that SecKeyGetBlockSize returns 16 and in java cipher.blocksize returns 5. I think the other 11 bytes are reserved for the pkcs1 padding, but how can force the same behaviour in ios/objc ? 到目前为止我遇到的唯一区别是SecKeyGetBlockSize returns 16并且在java中cipher.blocksize返回5.我认为其他11个字节是为pkcs1填充保留的,但是如何在ios/objc强制执行相同的行为?

Try up with splitting cipher text into multiple parts so that each contains 16 char long and separately decode them. 尝试将密文分成多个部分,以便每个部分包含16个字符长并分别解码它们。 I too faced the same problem but that was in PHP for a long time and above trick worked for me. 我也遇到了同样的问题,但是在PHP中存在了很长时间,而且上面的技巧对我有用。

This may be help you to get-out of the problem. 这可能有助于您摆脱问题。

Decoding the Base64 key gives: 解码Base64密钥给出:

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

Interpreting this as DER-encoded ASN.1, we find: 将其解释为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

Where the BITSTRING also seems to contain DER-encoded ASN.1: 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

Walking through the IOS code, you can see that it is parsing the DER-encoded ASN.1. 通过IOS代码,您可以看到它正在解析DER编码的ASN.1。 It correctly identifies the first two SEQUENCE tags, and skips over the OID field without even verifying that it is an OID. 它正确识别前两个SEQUENCE标记,并跳过OID字段,甚至不验证它是否为OID。 Then the problem occurs: the IOS code expects the next tag to be BITSTRING(0x03)---but in our data, we have an additional NULL(0x05) field to denote that the public exponent is implicit. 然后出现问题:IOS代码期望下一个标记为BITSTRING(0x03)---但在我们的数据中,我们有一个额外的NULL(0x05)字段来表示公共指数是隐式的。 The IOS code raises an exception upon encountering the 0x05 tag. IOS代码在遇到0x05标记时引发异常。 If the NULL weren't there, we see that the IOS code would have successfully extracted the contents of the BITSTRING. 如果没有NULL,我们会看到IOS代码已经成功提取了BITSTRING的内容。

So: either the NULL is an optional field, and the IOS code isn't permitting it, or the IOS code is expecting a different ASN.1 structure. 所以:NULL是一个可选字段,IOS代码不允许它,或者IOS代码期望不同的ASN.1结构。 For example, it appears that the BITSTRING is also a DER-encoded ASN.1 INTEGER (presumably the RSA modulus). 例如,看起来BITSTRING也是DER编码的ASN.1 INTEGER(可能是RSA模数)。 Yet the IOS code makes no attempt to parse it. 然而IOS代码没有尝试解析它。 It may be that the IOS SecKeyEncrypt routine expects this format for the modulus, or it may be that the caller is supposed to extract the raw bytes of the modulus. 可能是IOS SecKeyEncrypt例程期望模数的这种格式,或者可能是调用者应该提取模数的原始字节。

So there's a little bit of experimentation still needed. 所以仍然需要一些实验。 But the following additional conditional is definately necessary if this code is to parse the supplied data object: 但是,如果此代码要解析提供的数据对象,则必须使用以下附加条件:

/* 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)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM