在 iOS 上加密和在 Node.js 上使用 RAW RSA 解密时遇到问题

Posted

技术标签:

【中文标题】在 iOS 上加密和在 Node.js 上使用 RAW RSA 解密时遇到问题【英文标题】:Having Trouble Encrypting on iOS and decrypting on Node.js using RAW RSA 【发布时间】:2016-12-22 00:59:34 【问题描述】:

我正在尝试在 ios 端加密某些内容并在我的 node.js 服务器上对其进行解密。在服务器上,我正在使用库伪造。我能够在 node.js 上加密并解密它,这很有效。我这样加密:const encryptedPassword = publicKey.encrypt(password, 'RAW'); 并像这样解密:const password = privateKey.decrypt(encryptedPassword, 'RAW');

现在,我想在我的 iOS 应用程序上加密,而不是在服务器中加密,但仍使用相同的方式解密。我找到了这个库,swift-rsautils。 https://github.com/btnguyen2k/swift-rsautils/blob/master/Swift-RSAUtils/RSAUtils.swift 它有一个叫做encryptWithRSAKey 的函数,这就是我正在使用的。由于是原始加密,我尝试传入填充 SecPaddingNone。但是,不幸的是它不起作用,我无法在服务器上解密。错误信息是无效长度,base64 数据的长度看起来确实大了很多。有谁知道我该如何解决这个问题?

这是我的 iOS 代码:

let dataString = text.dataUsingEncoding(NSUTF8StringEncoding)
let certificateLabel = "certificate"
let certificateRef = self.getCertificateFromKeyChain(certificateLabel)
let certificateData = self.getDataFromCertificate(certificateRef)
let cryptoImportExportManager = CryptoExportImportManager()
let publicKeyRef = cryptoImportExportManager.importPublicKeyReferenceFromDERCertificate(certificateData)
let encryptedData = self.encryptWithRSAKey(data, rsaKeyRef: publicKeyRef!, padding: SecPadding.None)
let base64EncryptedString = encryptedData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))

然后我将这个 base64 加密字符串发送到服务器并尝试使用私钥解密。不幸的是,它不起作用。

【问题讨论】:

请不要使用 RAW RSA。这本教科书 RSA 完全不安全。 RSA 需要像 OAEP 这样的安全填充。请记住,RSA 只能加密有限数量的数据。如果要加密大于密钥大小的数据,则需要使用hybrid encryption。 是否有理由使用 RSA 非对称加密而不是对称加密,例如 AES 和/或使用 HTTPS? 我已经设置好一切来使用 RSA。 RSA 真的那么糟糕吗? 假设我知道我在做什么或者只是在做实验。如何在 iOS 上进行 Raw RSA 加密? RSA 的速度要慢得多,并且可以加密的数据被限制为小于密钥大小。等效安全性的密钥要长得多:AES 128 位与 RSA 3072 位等效。 AES 使用一个密钥,而 RSA 使用两个密钥。选择 RSA 的一些原因是需要单独的公钥和私钥以及 PKI(公钥基础设施)。缺乏对 RSA 的迫切需求,请使用 AES。 【参考方案1】:

这不是您确切问题的答案,因为我没有使用过那个特定的库,但我在 javascript 和 node.js 中玩了一点加密。

我能够实现 eccjs 库,它是斯坦福 JavaScript 加密库 (SJCL),支持非对称椭圆曲线。

在 node.js 方面:

var ecc = require('eccjs');
var cryptoKeys = ecc.generate(ecc.ENC_DEC); //crypto_keys.enc is the pubic key for encoding. crypto_keys.dec is the private key for decoding.
//send the public key to the client
app.get('/PublicKey', function(req, res)
    res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
    res.setHeader('Expires', '-1');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Content-type', 'text/plain');
    res.send('var publicKey = ' + JSON.stringify(cryptoKeys.enc) + ';');    
);

//authenticate a user name and a password (encrypted by client) against the domain controller
app.get('/Authenticate', function(req, res)
    res.setHeader('Content-type', 'text/plain');
    var url = "ldap://na-us-dc01.am.corp.airliquide.com";
    var userPrincipalName = req.query.username + "@US-AIRLIQUIDE";
    try
    
        var cipherMessage = JSON.parse(req.query.encryptedPassword);
        var password = ecc.decrypt(cryptoKeys.dec, cipherMessage);

        //... Authentication goes here ...
    
    catch(err)
    
        console.log("Error with authentication: ",err);
        res.send("Error with authentication: " + JSON.stringify(err,null,' '));
    
);

在客户端:

<script src="ecc.js"></script>
<script src="../PublicKey"></script> <!-- This returns the variable publicKey which has been set equal to the server's public key -->
<script>

function login() 
    var plainTextPassword = document.getElementById('password').value;
    var cipherTextPassword = ecc.encrypt(publicKey, plainTextPassword);
    var username = document.getElementById('name').value;
    console.log(ecc, publicKey, cipherTextPassword);
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = (function() 
        if (xhttp.readyState == 4 && xhttp.status == 200) 
            document.getElementById('result').innerhtml = xhttp.responseText;
            console.log("Response: " + xhttp.responseText);
        
    ).bind(this);
    xhttp.open("GET", "../Authenticate?username=" + username + "&encryptedPassword=" + JSON.stringify(cipherTextPassword), true);
    xhttp.send();   


</script>

我确信这个解决方案并不完全安全,我最终没有使用它,而是实现了 HTTPS。但是,如果这是您的最终目标,这应该为您提供进行自己的非对称加密所需的部分。

【讨论】:

我的问题是在 iOS 上做的。我知道如何做原始 rsa 并在其他地方做过,只是无法让它在 iOS 上运行。【参考方案2】:

SecPadding.None 已从 Swift3 中删除,并且 Swift-RSAUtils 的代码已更改,因此我无法重现您的问题。 但是我可以使用以下代码加密然后解密数据:

let data = "Data to be encrypted".data(using: String.Encoding.utf8)!
let e = RSAUtils.encryptWithRSAKey(data, rsaKeyRef: publicSecKeyRef, padding: SecPadding())
let d = try! RSAUtils.decryptWithRSAPrivateKey(encryptedData: e!, privkeyBase64: privkey)

您可以在https://github.com/btnguyen2k/swiftutils 使用最新版本的 Swift-RSAUtils 重试吗?

编辑:我注意到您收到错误“无效消息长度”。请注意,RSA 无法一次性加密大量数据。它可以加密长达key's size - 11 长度的消息。 为了解决这个限制,Swift-RSAUtils 将长数据分成小块,加密每个块并将它们合并在一起。因此,在服务器端,您应该做类似的事情:将加密数据拆分为 key's size 的块,解密每个块并将它们合并为最终结果。

【讨论】:

我想出了怎么做,问题是库正在添加填充,即使它是 SecPadding.None,但创建者修复了它。

以上是关于在 iOS 上加密和在 Node.js 上使用 RAW RSA 解密时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

无法在 node.js 上安装任何加密包

为 PGP 加密和在不同设备上签名导出哪个密钥?

Node.js 服务器到服务器加密

使用 socket.io/node.js 在网页上显示流式 Twitter

Node.js进阶:5分钟入门非对称加密方法

在 Openshift 上使用 Node.js 发送 iOS 推送通知