php openssl_seal 相当于 Node.js

php openssl_seal equivalent in Node.js

我在 php 中有一个代码片段,我想将其移至 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

我设法找到了 openssl_signcrypto 包的等效实现,但没有找到 openssl_seal.

LE added working solution as an answer

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

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

  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. 使用生成的密钥加密数据,然后使用给定的 public 密钥加密该密钥。我们希望发回加密的详细信息和加密的密钥,因为我们希望另一端的用户拥有私钥。
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();
    });
}

希望这能回答问题。

对我有用的最终版本。我的问题是我使用 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'),
  };
};