Подтвердить что ты не робот

Разница между кодировкой PKCS1-padding/RSA ios objc и java

Я разрабатываю приложение для iOS и Android. Я относительно новичок в криптографических задачах и в течение последних 3 дней. Я продолжаю ударять головой о стену, потому что Im не способен запустить шифрование 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, но как заставить такое поведение в ios/objc?

4b9b3361

Ответ 1

Попробуйте разделить шифрованный текст на несколько частей, чтобы каждый из них содержал 16 char длинных и отдельно декодированных. Я тоже сталкивался с той же проблемой, но это было в PHP в течение длительного времени, и выше трюк работал у меня.

Это может помочь вам избавиться от проблемы.

Ответ 2

Декодирование ключа Base64 дает:

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

Интерпретируя это как ASN.1 с кодированием DER, мы находим:

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 также, похоже, содержит ASN.1 с кодировкой DER:

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, вы можете видеть, что он анализирует ASN.1 с кодировкой DER. Он правильно идентифицирует первые два тега SEQUENCE и пропускает поле OID, даже не проверяя, что это OID. Затем возникает проблема: код IOS ожидает, что следующий тег будет BITSTRING (0x03) --- но в наших данных у нас есть дополнительное поле NULL (0x05), чтобы обозначить, что публичный показатель неявный. Код IOS вызывает исключение при встрече с тегом 0x05. Если NULL не было, мы видим, что код IOS успешно извлек бы содержимое BITSTRING.

Итак: либо NULL является необязательным полем, и IOS-код не разрешает, либо код IOS ожидает другую структуру ASN.1. Например, кажется, что BITSTRING также представляет собой кодированный ASE.1 INTEGER с DER (предположительно модуль 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)