【问题标题】:RSA algorithm on swift and javaswift和java上的RSA算法
【发布时间】:2018-01-31 11:26:01
【问题描述】:

我需要在 IOS 设备上为 RSA 算法生成公钥/私钥,并将公钥与加密文本一起发送到服务器。服务器必须读取公钥并解密用户消息。

我在 swift 上有代码:

 func generateKeys(){
    var publicKey: SecKey?
    var privateKey: SecKey?

    let publicKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"publicTag".data(using: String.Encoding.utf8)! as NSObject]
    let privateKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"privateTag".data(using: String.Encoding.utf8)! as NSObject]

    var keyPairAttr = [NSObject: NSObject]()
    keyPairAttr[kSecAttrKeyType] = kSecAttrKeyTypeRSA
    keyPairAttr[kSecAttrKeySizeInBits] = 4096 as NSObject
    keyPairAttr[kSecPublicKeyAttrs] = publicKeyAttr as NSObject
    keyPairAttr[kSecPrivateKeyAttrs] = privateKeyAttr as NSObject

    _ = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)

    var error:Unmanaged<CFError>?
    if #available(iOS 10.0, *) {
        if let cfdata = SecKeyCopyExternalRepresentation(publicKey!, &error) {
            let data:Data = cfdata as Data
            let b64Key = data.base64EncodedString(options: .lineLength64Characters)
            print("public base 64 : \n\(b64Key)")
        }
        if let cfdata = SecKeyCopyExternalRepresentation(privateKey!, &error) {
            let data:Data = cfdata as Data
            let b64Key = data.base64EncodedString(options: .lineLength64Characters)
            print("private base 64 : \n\(b64Key)")
        }
    }
    let encrypted = encryptBase64(text: "test", key: publicKey!)
    let decrypted = decpryptBase64(encrpted: encrypted, key: privateKey!)

    print("decrypted \(String(describing: decrypted))")

    self.dismiss(animated: true, completion: nil);
}


func encryptBase64(text: String, key: SecKey) -> String {
    let plainBuffer = [UInt8](text.utf8)
    var cipherBufferSize : Int = Int(SecKeyGetBlockSize(key))
    var cipherBuffer = [UInt8](repeating:0, count:Int(cipherBufferSize))

    // Encrypto  should less than key length
    let status = SecKeyEncrypt(key, SecPadding.PKCS1, plainBuffer, plainBuffer.count, &cipherBuffer, &cipherBufferSize)
    if (status != errSecSuccess) {
        print("Failed Encryption")
    }

    let mudata = NSData(bytes: &cipherBuffer, length: cipherBufferSize)
    return mudata.base64EncodedString()
}

func decpryptBase64(encrpted: String, key: SecKey) -> String? {
    let data : NSData = NSData(base64Encoded: encrpted, options: .ignoreUnknownCharacters)!
    let count = data.length / MemoryLayout<UInt8>.size
    var array = [UInt8](repeating: 0, count: count)
    data.getBytes(&array, length:count * MemoryLayout<UInt8>.size)

    var plaintextBufferSize = Int(SecKeyGetBlockSize(key))
    var plaintextBuffer = [UInt8](repeating:0, count:Int(plaintextBufferSize))

    let status = SecKeyDecrypt(key, SecPadding.PKCS1, array, plaintextBufferSize, &plaintextBuffer, &plaintextBufferSize)

    if (status != errSecSuccess) {
        print("Failed Decrypt")
        return nil
    }
    return NSString(bytes: &plaintextBuffer, length: plaintextBufferSize, encoding: String.Encoding.utf8.rawValue)! as String
}

此代码返回 PKCS1 中的公钥。我找到了图书馆:SwCrypt 此代码帮助我将 PKCS1 转换为 PKCS8 并使用 java 读取公钥

SwKeyConvert.PublicKey.pemToPKCS1DER(publicKeyPEM)

但我无法解密用户消息。你能帮我解密消息吗?我写了小单元测试。

import org.junit.Test;

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import static org.junit.Assert.assertNotNull;

public class TestExample {
    String publicKeyContent = "MIMAAiMwDQYJKoZIhvcNAQEBBQADgwACDwAwggIKAoICAQC4K4zr1jTi4SSypXbrNeGd2HbYlrDRIPsPcL5a4JwGUKXwi+Rpf8Xh0D4dcRRH+Rtd5F66aqdGnhCBKtU5XsmlT+QssIggihI0iF3LEPsMlKapDrDdSbWmuitVDSSlulReMcN3hEUl8AzlNyu817snZtYESiFxm87QV6xZAcrWzvIdyiStBbngCT/v76tOZDX56IIRGoLMi3WND7538PqqYheh2+oZk05O+Bf5LZc6YteTRLLOSyIIxesoABo8tvaFyIo2ihMcnDRnGAzOMNTLXiQdj2scAMCVr3oiLpU48+Iw8ptOUBDQioW15FsYd3ugZhUX+/mFtMFsYkJyYjyG5HCqAs2/wm6eIjjy1QQwUF2hB8Z7sqyF5KrVZOv6Q7+pB83tT02ZXcDXCdsiP10G3sA4kjc/r9TuQHjCIwZa1LO4tPaO8qAzlROHIkQ4FhdaAM9U9DUq3nBywQLcEVQmXeH1OA1ve96QbMQoN+SRPh0Kq6W0U4TbzvMskQ7bePKDjiWP2fdtgSfrnOsyJaLi04n+hDsgiMfd4N9tauSMpCY6H9l7yYPc5Z+3qG2ANhteZGa7wT1OZoGLkZV0OurnA4xkzwcB7h0RVEvABB9dtl6S60FK1NELQy6sC/HCcivo9sJ+C1g2Sln+8qEdiju86X5ja5pGiRhJAxwSp2ZKgwIDAQAB";
    String encryptedMessage = "g81SOC9XOD9zq5qfyhkdP/7ronNb82g3ueDtEh711L43zPSgrFksLEdIud/1fiDcV6N97RD41vb/iXtCg2/Gu6XliEhCaoG28reetG1cBndKF9UzQw9cYChp54S1wnhBkAAZQ4Of3c77DtPBCL4gcgv2ilBTm7o+NR2wXunfJ7Olbbau+7C1pa+Qv/+sz45r4gJmQ1MfGjHtw9e/U/3vjL9BfCEPn9Mo2zAZhkI81S0Ewth+csHwb3YTlE8mtHni1fvLRVXjvHk+57U3keoYPZk+93ytFL6pqkWMk+9VbLuUFHXn1mpSMiEr9GRN6XKRvEbbPp5lI9WjwRvtWfmRm5gLY76QinTrPb0KJg7oWmEoQie5o9W6MOkD+8vYV/SkkLT855SB3O57QLKCZmlSPlccE6GWfglHhAwRwrcTDY1bO/xH38gvYYPaAJMtJKtOVrqGxNkIUPwCCkdBa9JQwDSyTYxeh8AxC0ACs9cYVjMPrmC9zIZuRbmcneIGSugtzMZmI9qbLtW1aMlWuGrVyVhJlcCZuTJXWyBgx8xj8coX9YwUXSi1A4dL/Hl5Sme+HhAQs7OcH6ZZpsPmIIozXxHgOMhUo8k++cWg6+pudSoB2tr4NhxX/ID2jd1ELsg1C6mbxaKaGgXwfU9w4ZngbRxGTBlKWXwUP/xBa5BARZ4=";

    @Test
    public void encryptTest() throws Exception {
        PublicKey publicKey = convertPublicKey(publicKeyContent);
        assertNotNull(publicKey);

        String s = decryptString(publicKey, encryptedMessage);
        assertNotNull(s);
    }

    private PublicKey convertPublicKey(String publicKey) throws RSAAlgorithmException {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //generate public key
            byte[] publicBytes = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
            return keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RSAAlgorithmException("Unable to generate public key from string " + publicKey + " . " + e.getMessage());
        }
    }

    private String decryptString(PublicKey publicKey, String value) throws Exception {
        byte[] decodedBytes;
        try {
            Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            c.init(Cipher.DECRYPT_MODE, publicKey);
            decodedBytes = c.doFinal(value.getBytes());
        } catch (Exception e) {
            System.out.println("Error = " + e);
            throw new Exception(e);
        }
        return new String(decodedBytes);
    }
}

我有下一个错误:

java.lang.Exception: javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes

【问题讨论】:

  • 首先尝试创建一个对称的实现。仅显示三行而没有任何调试或错误指示基本上是要求我们为您完成工作。尝试创建一个 MCVE!
  • 使用我的示例,您将获得私钥/公钥和 java,将此公钥复制到 java 和加密消息。
  • 解密需要私钥。但是只有 3 行没有加载密钥的 Java 行,我认为您只是从某个地方复制了这些行,然后要求提供解决方案。你说你不能解密:你得到什么错误信息?
  • 我在问题中添加了更多细节
  • 有趣,我认为公钥加密是一个错字,因为您还提到了 PKCS#8,这是私钥的格式。但是,您不能使用公钥解密 RSA。用公钥加密,用私钥解密。

标签: java cryptography rsa public-key-encryption


【解决方案1】:

在非对称密码系统中,您有一个 密钥对,其中包含 公钥私钥

你用公钥加密,用私钥解密。公钥可以(公开)与其他方共享,使他们能够向您发送加密消息。私钥是保密的,因此只有您可以解密使用您的公钥加密的消息。

您通常不会直接使用 RSA 对消息进行加密,因为消息必须比模数短,并且可能具有安全隐患。相反,您要做的是为对称加密方案生成一个随机密钥,例如 AES-256-CTR(或 AES-256-GCM,如果除了保密之外还需要身份验证),使用对称加密方案加密消息,使用非对称加密方案加密对称密码的密钥,并将(非对称)加密密钥和(对称)加密消息发送给接收方。

接收者将首先使用他/她的私钥来解密对称加密方案的密钥,然后使用它来解密实际消息。这有时被称为“混合加密”,它使消息(或多或少)任意长。

所以,你要做的就是以下。

  1. 您必须为加密消息的接收者生成一个密钥对。因此,如果您的通信是单向的(iOS 设备向服务器发送数据,但没有数据返回),您只需要为您的服务器生成一个密钥对。如果您的服务器需要回话,您还需要为您的客户端生成一个密钥对。

  2. 为了向服务器发送加密消息,客户端需要拥有服务器的公钥。因此,您必须以某种方式将其转移到那里。问题是这种传输需要安全,否则攻击者可能会冒充服务器,向您提供他/她的公钥(他/她知道私钥),拦截所有流量,用他/她的私钥解密,用服务器的公钥重新加密,然后传递给服务器。这称为中间人攻击,使攻击者能够拦截(并可能操纵)您与服务器之间的所有通信。因此,您最好的选择可能是根本不交换公钥,而是将它们嵌入到应用程序中。只要应用程序代码可以通过经过身份验证的方式共享,这将防止中间人攻击。

  3. 当您想向服务器发送消息时,生成一个随机对称加密密钥(使用 加密安全随机数生成器 - 这不是您的语言的默认“随机”函数),用它和适当的对称加密方案加密消息,您可以根据自己的要求选择(例如需要身份验证?然后使用 AES-GCM - 只需要保密?然后使用 AES-CTR)。大多数加密方案还需要一个随机(不可预测的)初始化向量,您也可以使用 CSPRNG 生成它并且必须将其发送给接收方,因为它是解密所必需的,但不需要保密。

  4. 使用非对称加密方案和服务器的公钥加密对称加密方案的密钥。 RSA-PKCS1 是“过时的”。我会尝试使用 RSA-OAEP,因为它具有更理想的安全属性。将加密的密钥发送到服务器。

  5. 服务器使用非对称加密方案和他的私钥(保持秘密)解密对称加密方案的密钥。然后它使用对称加密方案解密消息。

由于其中大部分都很复杂,而且很多微妙的细节都可能导致安全漏洞,我建议您不要自己实施。我建议您只使用 TLS(可能带有受限制的参数集)并实现自己的证书验证器,在其中将服务器的公钥与已知良好的值进行比较,以摆脱整个 PKI 东西,这既要花钱又是首先不是很安全。至少,我会这样做。

或者,如果您想推出自己的“专有”协议,您可以尝试使用对开发人员更友好的加密库之一,尤其是 NaCl。这会抽象出许多“血淋淋的细节”,并为您选择许多正常的默认值,这些默认值不能被覆盖,所有这些都使得实现不安全的协议变得更加困难。

请记住,这不是说你“太笨了”。这只是做这些事情的正确方法。谈到加密,“DIY”越少越好。加密越广泛,您使用的加密越多,它得到的审查就越多,缺陷得到修复的速度就越快,所以使用像 NaCl 这样的东西,它在成千上万的应用程序中使用,非常简洁。只要其他 NaCl 应用程序是安全的,您的应用程序(可能)也是安全的。当发现漏洞时,NaCl 将得到更新,您只需更新应用程序中的库并自动安全,因此您(几乎)无需内部审查和修补,您的 Windows脆弱性(通常)会很短。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-25
    • 1970-01-01
    • 2012-06-01
    相关资源
    最近更新 更多