首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >用iOS加密,用Java解密

用iOS加密,用Java解密
EN

Stack Overflow用户
提问于 2014-09-30 04:51:57
回答 2查看 9.7K关注 0票数 20

我有一个从Java服务器发送的公钥。在我解码和剥离base64报头之前,编码的字符串是匹配的。我使用SecItemAdd将公钥存储在密钥链中。

因此,我尝试使用Java中的公钥加密数据,并使用私钥解密数据。我在Java端使用SecKeyEncrypt,在Java端使用Cipher

我加密的是对称的AES密钥,它加密我的实际数据,所以密钥长度是16字节。当简单地对密钥进行base64编码时,一切都正常,所以我知道这个出了问题。

下面是我的iOS调用的一个示例:

代码语言:javascript
复制
OSStatus sanityCheck = SecKeyEncrypt(publicKey,
        kSecPaddingPKCS1,
        (const uint8_t *) [incomingData bytes],
        keyBufferSize,
        cipherBuffer,
        &cipherBufferSize
);

下面是我的Java调用的一个示例:

代码语言:javascript
复制
public static byte[] decryptMessage (byte[] message, PrivateKey privateKey, String algorithm) {
    if (message == null || privateKey == null) {
        return null;
    }
    Cipher cipher = createCipher(Cipher.DECRYPT_MODE, privateKey, algorithm, false);
    if (cipher == null) {
        return null;
    }

    try {
        return cipher.doFinal(message);
    }
    catch (IllegalBlockSizeException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (BadPaddingException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
}

private static Cipher createCipher (int mode, Key encryptionKey, String algorithm, boolean useBouncyCastle) {
    Cipher cipher;

    try {
        if (useBouncyCastle) {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher = Cipher.getInstance(algorithm, "BC");
        }
        else {
            cipher = Cipher.getInstance(algorithm);
        }
    }
    catch (NoSuchAlgorithmException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (NoSuchPaddingException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (NoSuchProviderException e) {
        e.printStackTrace();
        return null;
    }

    try {
        cipher.init(mode, encryptionKey);
    }
    catch (InvalidKeyException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }

    return cipher;
}

我尝试了这么多组合,但都不起作用。

RSA/ECB/PKCS1Padding

  • iOS:

  • iOS: PKCS1,Java:org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.)

  • iOS:

  • iOS: PKCS1,Java: RSA PKCS1,Java: RSA

  • iOS: PKCS1,Java: RSA/None/PKCS1Padding.pkcs1Padding.pkCS1Padding.pkCS1Padding.pkcs1Padding.pkcs1Padding.pkCS1Padding.pkCS1Padding.pkcs1Padding.pkCS1Padding.pk

我还尝试使用内部Java提供程序和BouncyCastle提供程序。每次都会抛出javax.crypto.BadPaddingException,但每种组合的消息都不同。一些显示Data must start with zero,而另一些则显示Message is larger than modulus

iOS: PKCS1, Java: RSA没有抛出异常,但是得到的解密的byte[]数组的长度应该是16,但是它的长度是256,这意味着填充没有被正确地剥离。

有人能帮帮忙吗?

*编辑*

当我做更多的测试时,我遇到了这个页面(http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/pkcs/pkcs1/RSACipher.html),它基本上告诉我RSA == RSA/None/PKCS1Padding。解密工作在没有例外的意义上,但我仍然得到一个解密的密钥,它的byte[]长度是256而不是16。

另一个有趣的地方。看起来,如果Java服务器拥有从iOS设备生成的公钥并使用Cipher.getInstance("RSA")加密,那么手机就能够使用RSAPKCS1正确地解码消息。

*编辑2个*

我已经阅读了这些教程,并在iOS端再次查看了我的代码:

据我所知,我的代码一切都是正确的。一个重要的区别是我保存密钥的方式,所以我尝试以另一种方式保存它:

代码语言:javascript
复制
    OSStatus error = noErr;
    CFTypeRef persistPeer = NULL;

    NSMutableDictionary * keyAttr = [[NSMutableDictionary alloc] init];

    keyAttr[(__bridge id) kSecClass] = (__bridge id) kSecClassKey;
    keyAttr[(__bridge id) kSecAttrKeyType] = (__bridge id) kSecAttrKeyTypeRSA;
    keyAttr[(__bridge id) kSecAttrApplicationTag] = [secKeyWrapper getKeyTag:serverPublicKeyTag];
    keyAttr[(__bridge id) kSecValueData] = strippedServerPublicKey;
    keyAttr[(__bridge id) kSecReturnPersistentRef] = @YES;

    error = SecItemAdd((__bridge CFDictionaryRef) keyAttr, (CFTypeRef *)&persistPeer);

    if (persistPeer == nil || ( error != noErr && error != errSecDuplicateItem)) {
        NSLog(@"Problem adding public key to keychain");
        return;
    }

    CFRelease(persistPeer);

保存成功,但最终结果是相同的:解密的AES密钥的长度仍然是256个字节,而不是16个字节。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-12-12 22:45:06

我也有同样的问题。

但是,在没有填充的情况下使用它不是一个好主意。

因此,在iOS上,将kSecPaddingNone替换为kSecPaddingOAEP,并在代码中使用RSA/NONE/OAEPWithSHA1AndMGF1Padding。这对我来说很管用。

票数 14
EN

Stack Overflow用户

发布于 2014-10-02 01:51:50

使用RSA/None/NoPadding的解决方案

好的,所以我让它工作了,但是没有填充的。这部分真的让我很沮丧,我把它留给其他人去帮助。也许我最终会在github上发布我所拥有的一个库,一个用于Obj-C,一个用于Java。这是我到目前为止所发现的。

TL;DR:将密钥保存到具有最少属性的密钥链中,以简化检索。使用SecKeyEncrypt加密,但使用kSecPaddingNone。在Java端使用BouncyCastle和RSA/None/NoPadding算法进行解密。

从Java向iOS发送RSA公钥

使用X.509证书

我想要验证直接发送公钥,剥离ASN.1头并保存是否真的做了它应该做的事情。因此,我考虑将公钥作为证书发送过来。我要感谢David Benko提供了一个加密库(https://github.com/DavidBenko/DBTransitEncryption),它帮助我完成了证书转换。我实际上没有使用他的库,因为1.我已经使用RNCryptor/JNCryptor进行AES加密,2.他没有Java端组件,所以我需要在那里写我自己的AES解密,我不想那样做。对于那些感兴趣并希望采用这种方法的人,下面是我的代码,用于在Java端创建证书,然后将该证书转换为iOS上的公钥:

*重要提示:请将e.printStackTrace()替换为真实的日志语句。我只是在测试和时使用它,而不是在生产中使用。

Java

代码语言:javascript
复制
public static X509Certificate generateCertificate (KeyPair newKeys) {
    Security.addProvider(new BouncyCastleProvider());
    Date startDate = new Date();
    Date expiryDate = new DateTime().plusYears(100).toDate();

    BigInteger serialNumber = new BigInteger(10, new Random());
    try {
        ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(newKeys
                                                                                                          .getPrivate());
        SubjectPublicKeyInfo subjectPublicKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance(newKeys
                                                                                                              .getPublic().getEncoded()
                                                                                                              ));
        X500Name dnName = new X500Name("CN=FoodJudge API Certificate");
        X509v1CertificateBuilder builder = new X509v1CertificateBuilder(dnName,
                                                                        serialNumber,
                                                                        startDate, expiryDate,
                                                                        dnName,
                                                                        subjectPublicKeyInfo);
        X509CertificateHolder holder = builder.build(sigGen);
        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
    }
    catch (OperatorCreationException e) {
        e.printStackTrace();
    }
    catch (CertificateException e) {
        e.printStackTrace();
    }
    return null;
}

Obj-C

代码语言:javascript
复制
- (SecKeyRef)extractPublicKeyFromCertificate:(NSData *)certificateBytes {
    if (certificateBytes == nil) {
        return nil;
    }

    SecCertificateRef certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef) certificateBytes);
    if (certificate == nil) {
        NSLog(@"Can not read certificate from data");
        return false;
    }

    SecTrustRef trust;
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);

    // release the certificate as we're done using it
    CFRelease(certificate);
    // release the policy
    CFRelease(policy);

    if (returnCode != errSecSuccess) {
        NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)returnCode);
        return nil;
    }

    SecTrustResultType trustResultType;
    returnCode = SecTrustEvaluate(trust, &trustResultType);
    if (returnCode != errSecSuccess) {
        // TODO log
        CFRelease(trust);
        return nil;
    }

    SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
    CFRelease(trust);

    if (publicKey == nil) {
        NSLog(@"SecTrustCopyPublicKey fail");
        return nil;
    }

    return publicKey;
}

使用RSA公钥

需要注意的是,您不需要将公钥作为证书发送。事实上,在发现公钥被错误地保存后(见下文),我还原了这段代码并将公钥保存到我的设备上。您需要去掉其中一篇博客文章中提到的ASN.1标头。这里重新发布了该代码(为清晰起见,进行了格式化)。

代码语言:javascript
复制
+ (NSData *)stripPublicKeyHeader:(NSData *)keyBits {
    // Skip ASN.1 public key header
    if (keyBits == nil) {
        return nil;
    }

    unsigned int len = [keyBits length];
    if (!len) {
        return nil;
    }

    unsigned char *c_key = (unsigned char *)[keyBits bytes];
    unsigned int  idx    = 0;

    if (c_key[idx++] != 0x30) {
        return nil;
    }

    if (c_key[idx] > 0x80) {
        idx += c_key[idx] - 0x80 + 1;
    }
    else {
        idx++;
    }

    if (idx >= len) {
        return nil;
    }

    if (c_key[idx] != 0x30) {
        return nil;
    }

    idx += 15;

    if (idx >= len - 2) {
        return nil;
    }

    if (c_key[idx++] != 0x03) {
        return nil;
    }

    if (c_key[idx] > 0x80) {
        idx += c_key[idx] - 0x80 + 1;
    }
    else {
        idx++;
    }

    if (idx >= len) {
        return nil;
    }

    if (c_key[idx++] != 0x00) {
        return nil;
    }

    if (idx >= len) {
        return nil;
    }

    // Now make a new NSData from this buffer
    return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}

所以我会像这样简单地保存密钥:

代码语言:javascript
复制
- (void)storeServerPublicKey:(NSString *)serverPublicKey {
    if (!serverPublicKey) {
        return;
    }
    SecKeyWrapper *secKeyWrapper = [SecKeyWrapper sharedWrapper];
    NSData *decryptedServerPublicKey = [[NSData alloc] initWithBase64EncodedString:serverPublicKey options:0];

    NSData *strippedServerPublicKey = [SecKeyWrapper stripPublicKeyHeader:decryptedServerPublicKey];
    if (!strippedServerPublicKey) {
        return;
    }
    [secKeyWrapper savePublicKeyToKeychain:strippedServerPublicKey tag:@"com.sampleapp.publickey"];
}

将RSA公钥保存到密钥链

真让人抓狂。事实证明,尽管我保存了钥匙链的钥匙,但我检索到的并不是我放进去的东西!当我比较我保存的base64密钥和我用来加密我的base64密钥的AES密钥时,我偶然发现了这一点。所以我发现最好简化保存密钥时使用的NSDictionary。这是我最终得到的结论:

代码语言:javascript
复制
- (void)savePublicKeyToKeychain:(NSData *)key tag:(NSString *)tagString {
    NSData *tag = [self getKeyTag:tagString];

    NSDictionary *saveDict = @{
            (__bridge id) kSecClass : (__bridge id) kSecClassKey,
            (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
            (__bridge id) kSecAttrApplicationTag : tag,
            (__bridge id) kSecAttrKeyClass : (__bridge id) kSecAttrKeyClassPublic,
            (__bridge id) kSecValueData : key
    };
    [self saveKeyToKeychain:saveDict tag:tagString];
}

- (void)saveKeyToKeychain:(NSDictionary *)saveDict tag:(NSString *)tagString {
    OSStatus sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
    if (sanityCheck != errSecSuccess) {
        if (sanityCheck == errSecDuplicateItem) {
            // delete the duplicate and save again
            sanityCheck = SecItemDelete((__bridge CFDictionaryRef) saveDict);
            sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, NULL);
        }
        if (sanityCheck != errSecSuccess) {
            NSLog(@"Problem saving the key to keychain, OSStatus == %d.", (int) sanityCheck);
        }
    }
    // remove from cache
    [keyCache removeObjectForKey:tagString];
}

要检索我的密钥,我使用以下方法:

代码语言:javascript
复制
 - (SecKeyRef)getKeyRef:(NSString *)tagString isPrivate:(BOOL)isPrivate {
     NSData *tag = [self getKeyTag:tagString];

     id keyClass = (__bridge id) kSecAttrKeyClassPublic;
     if (isPrivate) {
         keyClass = (__bridge id) kSecAttrKeyClassPrivate;
     }

     NSDictionary *queryDict = @{
             (__bridge id) kSecClass : (__bridge id) kSecClassKey,
             (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
             (__bridge id) kSecAttrApplicationTag : tag,
             (__bridge id) kSecAttrKeyClass : keyClass,
             (__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue
     };
     return [self getKeyRef:queryDict tag:tagString];
 }

- (SecKeyRef)getKeyRef:(NSDictionary *)query tag:(NSString *)tagString {
    SecKeyRef keyReference = NULL;
    OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef *) &keyReference);
    if (sanityCheck != errSecSuccess) {
        NSLog(@"Error trying to retrieve key from keychain. tag: %@. sanityCheck: %li", tagString, sanityCheck);
        return nil;
    }
    return keyReference;
}

最后,我只能让它在没有填充的情况下工作。我不确定为什么BouncyCastle不能删除填充,所以如果有人有任何见解,请让我知道。

以下是我的加密代码(修改自David Benko):

代码语言:javascript
复制
- (NSData *)encryptData:(NSData *)content usingPublicKey:(NSString *)publicKeyTag {
    SecKeyRef publicKey = [self getKeyRef:publicKeyTag isPrivate:NO];
    NSData *keyBits = [self getKeyBitsFromKey:publicKey];
    NSString *keyString = [keyBits base64EncodedStringWithOptions:0];
    NSAssert(publicKey != nil,@"Public key can not be nil");

    size_t cipherLen = SecKeyGetBlockSize(publicKey); // convert to byte
    void *cipher = malloc(cipherLen);
    size_t maxPlainLen = cipherLen - 12;

    size_t plainLen = [content length];
    if (plainLen > maxPlainLen) {
        NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
        return nil;
    }

    void *plain = malloc(plainLen);
    [content getBytes:plain
               length:plainLen];

    OSStatus returnCode = SecKeyEncrypt(publicKey, kSecPaddingNone, plain,
            plainLen, cipher, &cipherLen);

    NSData *result = nil;
    if (returnCode != errSecSuccess) {
        NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)returnCode);
    }
    else {
        result = [NSData dataWithBytes:cipher
                                length:cipherLen];
    }

    free(plain);
    free(cipher);

    return result;
}

下面是我如何在Java端解密:

代码语言:javascript
复制
private Response authenticate (String encryptedSymmetricString) {
    byte[] encryptedSymmetricKey = Base64.decodeBase64(encryptedSymmetricKeyString);
    String privateKey = Server.getServerPrivateKey();
    byte[] decryptedSymmetricKey = KeyHandler.decryptMessage(encryptedSymmetricKey, privateKey,
                                                             KeyHandler.ASYMMETRIC_CIPHER_ALGORITHM);
}

public static byte[] decryptMessage (byte[] message, String privateKeyString, String algorithm) {
    if (message == null || privateKeyString == null) {
        return null;
    }
    PrivateKey privateKey = getPrivateKey(privateKeyString);
    return decryptMessage(message, privateKey, algorithm);
}

public static byte[] decryptMessage (byte[] message, PrivateKey privateKey, String algorithm) {
    if (message == null || privateKey == null) {
        return null;
    }
    Cipher cipher = createCipher(Cipher.DECRYPT_MODE, privateKey, algorithm, true);
    if (cipher == null) {
        return null;
    }

    try {
        return cipher.doFinal(message);
    }
    catch (IllegalBlockSizeException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
    catch (BadPaddingException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        return null;
    }
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26108672

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档