使用 openpgp 在转换后加密流

Posted

技术标签:

【中文标题】使用 openpgp 在转换后加密流【英文标题】:Encrypt a stream after a transform using openpgp 【发布时间】:2022-01-22 15:11:58 【问题描述】:

我正在使用以下管道从 Aurora 流式传输数据,将其转换为 csv,然后将其发送到 S3。

可读的 knex 流:

const getQueryStream = (organizationId) => 
  db.select('*')
    .from('users')
    .where('organization_id', organizationId)
    .stream();

转换数据:

const toCSVTransform = (fields) => new stream.Transform(
  objectMode: true,
  transform: (row, encoding, callback) => 
    let rowAsArr = [];
    for(let i = 0; i < fields.length; i++) 
      rowAsArr.push(row[fields[i]]);
    
    callback(null, `$rowAsArr.join(',')\n`);
  
);

管道:

stream.pipeline(
    dbStream,
    toCSVTransform(['first_name', 'last_name', 'email']),
    s3WritableStream,
    (err) => 
        if (err) 
            console.error('Pipeline failed.', err)
         else 
            console.log('Pipeline succeeded.')
        
    
)

这可以按原样工作,但我们有一个额外的要求,即使用 PGP 加密来加密文件。我的想法是在toCSVTransform 之后在管道中增加一个步骤来进行加密。 npm 包openpgp 支持流,但我不知道如何将它用于管道。

来自openpgp 文档,这里是如何使用将可读流传递给openpgp.encrypt 函数的示例:

const readableStream = new ReadableStream(
    start(controller) 
        controller.enqueue('Hello, world!');
        controller.close();
    
);

const encrypted = await openpgp.encrypt(
    message: await openpgp.createMessage( text: readableStream ), // input as Message object
    encryptionKeys: publicKey,
    signingKeys: privateKey // optional
);

我见过的所有示例都只是将可读流传递给 encrypt 函数。但我需要在将数据发送到 s3 之前将数据转换为数据。

有没有办法让我将toCSVTransform 流传递给openpgp.encrypt 方法?

似乎我想将可读的dbStream 和转换流toCSVTransform 组合成一个流并将其传递给 openpgp.encrypt 函数。

我注意到 node.js 有一个 stream.compose 方法,但它目前只是实验性的,所以它不是一个真正的选择。

**** 编辑:可能的解决方案 看起来我可以使用 pipe() 在将流传递给 openpgp.encrypt 方法之前对其进行转换:

const encrypted = await openpgp.encrypt(
    message: await openpgp.createMessage( text: dbStream.pipe(toCSVTransform) ), // input as Message object
    encryptionKeys: publicKey,
    signingKeys: privateKey // optional
);

【问题讨论】:

【参考方案1】:

您所拥有的内容大致正确,但 encrypted 将是一个 Stream。

这将起作用:

const encryptedPrivateKey = await openpgp.readPrivateKey(armoredKey);
const signingKey = await openpgp.decryptKey(
  privateKey: encryptedPrivateKey,
  passphrase,
)

const encrypt = async (encryptionKeys, signingKeys, readableStream) => await openpgp.encrypt(
  message: await openpgp.createMessage(text: readableStream),
  encryptionKeys,
  signingKeys,
);

stream.pipeline(
    await encrypt(encryptionKey, signingKey, stream.pipeline(
      dbStream,
      toCSVTransform(['first_name', 'last_name', 'email']),
    )),
    s3WritableStream,
    (err) => 
        if (err) 
            console.error('Pipeline failed.', err)
         else 
            console.log('Pipeline succeeded.')
        
    
)

不幸的是,没有(简单的)方法可以包装 openpgp 使其可以直接插入管道中。

如果您对对称加密没问题,那么更简洁的解决方案是使用crypto

const encrypter = crypto.createCipheriv(algo, key, iv)

stream.pipeline(
    dbStream,
    toCSVTransform(['first_name', 'last_name', 'email']),
    encrypter,
    s3WritableStream,
    (err) => 
        if (err) 
            console.error('Pipeline failed.', err)
         else 
            console.log('Pipeline succeeded.')
        
    
)

【讨论】:

不幸的是,pgp 是必需的。此外,这些文件需要签名,而且似乎也没有简单的方法可以做到这一点。 await encrypt 管道步骤需要嵌套在 await sign 步骤中。也许这应该是另一个问题,但是有没有更简单的方法来进行加密和签名? 你可以同时做这两个!只需提供signingKeys 作为encrypt() 的选项即可。示例已更新。 这似乎有效,但 Intellij 检查报告await.encrypt 行上的签名不匹配。 Argument type WebStream&lt;string&gt; | NodeStream&lt;string&gt; | string is not assignable to parameter type NodeJS.ReadableStream   Type string is not assignable to type NodeJS.ReadableStream。该警告对我来说没有意义,因为 openpgp.encrypt 接受 NodeStream @navig8tr 我认为我们在同一页上,但为避免歧义,openpgp.encrypt() 不接受 Streamopenpgp.createMessage() 接受 in the text value of options。但它也接受string,因此如果有疑问,此消息似乎不正确,请使用 Typescript。 您删除了原始代码中声明名为readableStream 的变量的行,对吗?消息是否与另一行代码有关?

以上是关于使用 openpgp 在转换后加密流的主要内容,如果未能解决你的问题,请参考以下文章

OpenPGP协议的一个JavaScript实现:OpenPGP.js

如何使用 OpenPGP.js 在 Objective-C 中加密/解密 PGP 消息

Openpgp 多密钥加密

无法加密 OpenPGP.js 中的字符串

带有 Ionic 2 Angular 2 和 TypeScript 的 OpenPGP

从 openpgp 生成公钥私钥