【问题标题】:php openssl_seal equivalent in Node.jsNode.js 中的 php openssl_seal 等效项
【发布时间】:2020-01-29 16:27:41
【问题描述】:

我在 php 中有一个代码 sn-p,我想将其移入 node.js,但我似乎找不到正确的方法。

class  EncryptService
{
    const PUBLIC_CERT_PATH = 'cert/public.cer';
    const PRIVATE_CERT_PATH = 'cert/private.key';
    const ERROR_LOAD_X509_CERTIFICATE = 0x10000001;
    const ERROR_ENCRYPT_DATA = 0x10000002;

    public $outEncData = null;
    public $outEnvKey = null;
    public $srcData;

    public function encrypt()
    {
        $publicKey = openssl_pkey_get_public(self::PUBLIC_CERT_PATH);

        if ($publicKey === false) {
            $publicKey = openssl_pkey_get_public("file://".self::PUBLIC_CERT_PATH);
        }
        if ($publicKey === false) {
            $errorMessage = "Error while loading X509 public key certificate! Reason:";

            while (($errorString = openssl_error_string())) {
                $errorMessage .= $errorString . "\n";
            }
            throw new Exception($errorMessage, self::ERROR_LOAD_X509_CERTIFICATE);
        }

        $publicKeys = array($publicKey);
        $encData = null;
        $envKeys = null;
        $result = openssl_seal($this->srcData, $encData, $envKeys, $publicKeys);
        if ($result === false)
        {
            $this->outEncData = null;
            $this->outEnvKey = null;
            $errorMessage = "Error while encrypting data! Reason:";
            while (($errorString = openssl_error_string()))
            {
                $errorMessage .= $errorString . "\n";
            }
            throw new Exception($errorMessage, self::ERROR_ENCRYPT_DATA);
        }
        $this->outEncData = base64_encode($encData);
        $this->outEnvKey = base64_encode($envKeys[0]);
    }
};

问题是我无法在任何地方的 Javascript 中找到 openssl_sign 的实现。我确实需要保留这个结构,因为我同时使用了outEncDataoutEnvKey

我设法用crypto 包找到openssl_sign 的等效实现,但openssl_seal 没有。

LE 添加了工作解决方案作为答案

【问题讨论】:

  • 默认情况下,openssl_seal 似乎使用 RC4 密码,该密码已被弃用,因此您可能需要进行各种恶作剧才能使其正常工作。
  • 但是如果操作系统有 RC4 看起来你应该可以用 crypto.createCipheriv("RC4", myGeneratedKey, null) 方法 nodejs.org/api/… 替换这段代码
  • 感谢@Aivaras 的回复。我像你说的那样尝试了,结果如下。 const cypher = crypto.createCipheriv("RC4", public_key, null); let encrypted = cypher.update(message, 'utf8', 'base64'); encrypted += cypher.final('base64'); 但它返回的哈希值始终保持不变,我错过了env_key。在php 中,hash 和 env 总是为相同的值而变化。我在代码中做错了什么吗?
  • 我认为在 phpenv_key 是在幕后生成的,而在 Node 中你需要自己创建它,然后使用它而不是你的 public_key
  • @Aivaras 你能用一些代码回复一下吗?

标签: javascript node.js openssl cryptojs


【解决方案1】:

对我有用的最终版本和工作版本。我的问题是我使用 128 位随机密钥加密数据,而不是 256 位最终工作。

加密在 JS 中工作,可以使用您的私钥在 php 中使用 openssl_open 解密,这是我在原始问题中提出的问题。

const crypto = require('crypto');
const path = require('path');
const fs = require('fs');

const encryptMessage = (message) => {
  const public_key = fs.readFileSync(`${appDir}/certs/sandbox.public.cer`, 'utf8');
  const rc4Key = Buffer.from(crypto.randomBytes(32), 'binary');
  const cipher = crypto.createCipheriv('RC4', rc4Key, null);

  let data = cipher.update(message, 'utf8', 'base64');
  cipher.final();

  const encryptedKey = crypto.publicEncrypt({
    key: public_key,
    padding: constants.RSA_PKCS1_PADDING
  }, rc4Key);

  return {
    'data': data,
    'env_key': encryptedKey.toString('base64'),
  };
};

【讨论】:

  • 如果您正在寻找更安全的 AES256 版本,我在这里提出了一个要点:gist.github.com/datashaman/d2f3b01196958b3adda9a62b5f75591c 我怀疑您需要在 final中添加 'base64' > 方法调用并将结果附加到现有数据中。
  • 它应该是data += cipher.final() 否则你最终会丢失一些数据:)
【解决方案2】:

好的,我花了一些时间来解决这个问题,简而言之,它现在在 repo 中:ivarprudnikov/node-crypto-rc4-encrypt-decrypt。但是我们想在这里遵循 SO 规则。

以下假设您拥有用于签署生成的密钥的公钥和用于测试是否一切正常的私钥。

  1. 随机生成的用于加密的密钥:
const crypto = require('crypto');

const generateRandomKeyAsync = async () => {
    return new Promise((resolve, reject) => {
        crypto.scrypt("password", "salt", 24, (err, derivedKey) => {
            if (err) reject(err);
            resolve(derivedKey.toString('hex'));
        });
    });
}
  1. 使用生成的密钥加密数据,然后使用给定的公钥加密该密钥。我们希望同时发回加密的详细信息和加密的密钥,因为我们希望另一方的用户拥有私钥。
const crypto = require('crypto');
const path = require('path');
const fs = require('fs');

const encryptKeyWithPubAsync = async (text) => {
    return new Promise((resolve) => {
        fs.readFile(path.resolve('./public_key.pem'), 'utf8', (err, publicKey) => {
            if (err) throw err;
            const buffer = Buffer.from(text, 'utf8');
            const encrypted = crypto.publicEncrypt(publicKey, buffer);
            resolve(encrypted.toString('base64'));  
        });
    });
}

const encryptStringAsync = async (clearText) => {
    const encryptionKey = await generateRandomKeyAsync();
    const cipher = await crypto.createCipheriv("RC4", encryptionKey, null);
    const encryptedKey = await encryptKeyWithPubAsync(encryptionKey);
    return new Promise((resolve, reject) => {
        let encryptedData = '';
        cipher.on('readable', () => {
          let chunk;
          while (null !== (chunk = cipher.read())) {
            encryptedData += chunk.toString('hex');
          }
        });
        cipher.on('end', () => {
          resolve([encryptedKey, encryptedData]); // return value
        });
        cipher.write(clearText);
        cipher.end();   
    });
}
  1. 所以现在我们可以加密细节了:
encryptStringAsync("foo bar baz")
   .then(details => {
        console.log(`encrypted val ${details[1]}, encrypted key ${details[0]}`);
    })

将打印如下内容:

encrypting foo bar baz
encrypted val b4c6c7a79712244fbe35d4, encrypted key bRnxH+/pMEKmYyvJuFeNWvK3u4g7X4cBaSMnhDgCI9iii186Eo9myfK4gOtHkjoDKbkhJ3YIErNBHpzBNc0rmZ9hy8Kur8uiHG6ai9K3ylr7sznDB/yvNLszKXsZxBYZL994wBo2fI7yfpi0B7y0QtHENiwE2t55MC71lCFmYtilth8oR4UjDNUOSrIu5QHJquYd7hF5TUtUnDtwpux6OnJ+go6sFQOTvX8YaezZ4Rmrjpj0Jzg+1xNGIIsWGnoZZhJPefc5uQU5tdtBtXEWdBa9LARpaXxlYGwutFk3KsBxM4Y5Rt2FkQ0Pca9ZZQPIVxLgwIy9EL9pDHtm5JtsVw==
  1. 要测试上述假设,首先需要使用私有密钥解密密钥:
const decryptKeyWithPrivateAsync = async (encryptedKey) => {
    return new Promise((resolve) => {
        fs.readFile(path.resolve('./private_key.pem'), 'utf8', (err, privateKey) => {
            if (err) throw err;
            const buffer = Buffer.from(encryptedKey, 'base64')
            const decrypted = crypto.privateDecrypt({
                key: privateKey.toString(),
                passphrase: '',
            }, buffer);
            resolve(decrypted.toString('utf8'));
        });
    });
}
  1. 密钥解密后可以解密消息:
const decryptWithEncryptedKey = async (encKey, encVal) => {
    const k = await decryptKeyWithPrivateAsync(encKey);
    const decipher = await crypto.createDecipheriv("RC4", k, null);
    return new Promise((resolve, reject) => {
        let decrypted = '';
        decipher.on('readable', () => {
          while (null !== (chunk = decipher.read())) {
            decrypted += chunk.toString('utf8');
          }
        });
        decipher.on('end', () => {
          resolve(decrypted); // return value
        });
        decipher.write(encVal, 'hex');
        decipher.end();
    });
}

希望这能回答问题。

【讨论】:

  • 您好,很抱歉我周末不在。我测试了它,理论上它应该可以工作,因为所有步骤都遵循但不幸的是它没有通过。从今天早上开始,我试图找出问题所在。我使用这些数据向第 3 方服务器发出请求。我将您的加密数据切换为base64,但它仍然无法正常工作。每次服务器返回数据解密失败。
  • 另外我无法测试解密,因为它无法读取我的私钥,我收到以下错误Error: error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error 我在crypto.privateDecrypt 方法中添加了padding:crypto.constants.RSA_PKCS1_PADDING,但它仍然不起作用跨度>
  • 我认为这个答案中的解决方案解决了这个问题。给定示例在 CI 服务器上成功加密/解密。您遇到的其他问题可以在另一个 SO 问题中解决,如“如何加载私钥......”或“如何解密......”或“我应该在缓冲区中使用哪种编码......”。
  • 好的,我换个说法。它不起作用。使用phpdataenv_key 可以被解码,至于js 的那些,它们不能被解码,使用完全相同的键,所以它并不能真正回答我的问题。这是一种方法,但不是有效的解决方案
  • 不幸的是,它确实可以在 CI 服务器上公开运行,不确定您是否检查过。我认为在您的情况下发生的情况取决于超出此问题范围的证书。为了测试已回答的方法,我生成了自己的证书,因为问题中没有提供它们。
猜你喜欢
  • 2013-02-23
  • 2022-01-14
  • 1970-01-01
  • 2011-10-12
  • 2016-08-23
  • 2019-03-28
  • 2018-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多