在客户端的浏览器上永久存储加密的私钥

Persistently storing an encrypted private key on the client's browser

提问人:Lakshya Raj 提问时间:7/11/2023 最后编辑:Lakshya Raj 更新时间:7/11/2023 访问量:54

问:

作者注:这个问题与多个类似问题不同,因为它特定于非常有限的浏览器沙箱,并专门询问密钥持久性

我有一个由密钥封装机制 (KEM) 提供支持的公钥身份验证系统,即 Kyber。当用户希望向服务器进行身份验证时,将发生以下过程。

TLDR 开始


请注意,此过程对问题并不完全重要,而是为了完整性而提供的。您可以安全地跳到 TLDR END。

  1. 客户端请求身份验证,因此服务器发起质询
  2. 服务器在质询中发送以下数据:
    • 秘密对称密钥的封装,它是从存储的公钥中获取的csspk
    • 共享(但不是机密)初始化向量iv
    • 目标、随机生成的消息m
    • 注意:以上所有项目都是新生成的,以前从未使用过
  3. 为了应对挑战,用户
    • 使用他们的密码将加密的私钥解密为psk
    • 用于将封装解密为密钥对称密钥skcss
    • 使用并加密成,生成一个ssivmeauthTag
    • 发回并eauthTag
  4. 为了验证用户的响应,服务器
    • 使用 和 获取和解密它,使用 g 接收解密的消息essivauthTad
    • 如果等于 ,则用户已成功通过身份验证,可以获得令牌dm
    • 如果不等于 ,则用户身份验证失败dm

这是促进此过程的代码(同样,不直接相关)

// generic shared functions
const kyber = require('crystals-kyber');
function buf(array) { return Buffer.from(array); }
function hex(buffer) { return '0x' + buffer.toString('hex'); }

// not shared, but exists before
let pk_sk = kyber.KeyGen1024();
let pk = pk_sk[0]; // stored on server
let sk = pk_sk[1]; // stored on client

/*** CURRENTLY IN SERVER ***/
// the user has requested an authentication challenge and wants to sign in as user 'u'
var u = "anyusernamehere";

// Generate a random symmetric key (ss) and its encapsulation (c)
let c_ss = kyber.Encrypt1024(pk);
let c = c_ss[0];
let ss1 = c_ss[1];
console.log("server symmetric", hex(ss1));

// decide on algorithm, initialization vector, and message
const crypto = require('crypto');
const algorithm = 'aes-256-ocb';
const iv = crypto.randomBytes(12);
const m = buf(crypto.getRandomValues(new Uint8Array(16)));
console.log("message", hex(m));

/*** SEND BACK algorithm, m, c, iv ***/
/*** CURRENTLY IN CLIENT ***/

// To decapsulate and obtain the same symmetric key
let ss2 = kyber.Decrypt1024(c, sk);
console.log("client symmetric", hex(ss2));

// Encrypting text using the symmetric key
const cipher = crypto.createCipheriv(algorithm, ss2, iv, {
    authTagLength: 16
}); 
let encrypted = cipher.update(m);
encrypted = Buffer.concat([encrypted, cipher.final()]);
const authTag = cipher.getAuthTag();
console.log("encrypted", hex(encrypted));
console.log("authTag", hex(authTag));

/*** SEND BACK encrypted, authTag ***/
/*** CURRENTLY IN SERVER ***/

// Decrypting text using the symmetric key
const decipher = crypto.createDecipheriv(algorithm, ss1, iv, {
    authTagLength: 16
});
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
console.log("decrypted", hex(decrypted));
console.log(hex(decrypted) === hex(m));
This is not intended to be run on the browser, as it is a NodeJS script.

下面是一些示例控制台输出:

> node .\kyber-auth.js
server symmetric 0x2be4644b917f9fac46b128a4965896cb1ebb6e0a42951c23e73b15d973e105e3
message 0xe1c317bdf1fab107cf7ca81ee676419e
client symmetric 0x2be4644b917f9fac46b128a4965896cb1ebb6e0a42951c23e73b15d973e105e3
encrypted 0x8b55d71ae76c87d731d86f9168fae831
authTag 0x9a38ce25e0de749da2a9507f6c7544d0
decrypted 0xe1c317bdf1fab107cf7ca81ee676419e
true

TLDR 结束


现在,一切就绪后,我们只需要存储数据即可。服务器存储密钥没有问题,但客户端受到限制(受浏览器沙箱限制)。

像 cookie 这样的传统方法很容易失败,当用户清除他们的浏览器数据时,加密的私钥与其他所有内容一起刷新。这是不可接受的,因为用户刚刚失去了对其帐户的访问权限。丢失您的帐户不可能这么容易。localStorage

我通过研究找到的解决方案是将加密的密钥文件下载到用户的文件系统中,并在准永久存储发生故障时请求它。还建议使用包含加密私钥的可扫描和可保存的二维码。然而,这两种解决方案似乎都非常迂回,对用户体验来说是可怕的。

我要求一种方法可以在客户端安全地存储加密的私钥。我所说的“安全”并不是说黑客不应该能够访问它,我的意思是用户不应该意外删除他们唯一的帐户密钥。如何达到这种持久性水平?还是我做错了什么?

编辑:我尝试从密码本身派生密钥,但 Kyber 不支持使用密码系统函数本身来执行此操作。

身份验证 存储 客户端 私钥

评论

0赞 Tangentially Perpendicular 7/11/2023
你在这里一无所获。用户可以刷新本地浏览器数据,或采取更剧烈的操作,例如在错误的位置、格式化磁盘或因故障或盗窃而丢失磁盘。如果您担心放在那里的东西会丢失,请找到不同的方法。del *.*
0赞 Lakshya Raj 7/11/2023
@TangentiallyPerpendicular 所以......这是否意味着公钥身份验证系统在处理客户端机器时存在根本缺陷?(通常情况如此)但是,我确实同意你的看法。你有没有一个特定的“不同的方法”?我已经编辑了这个问题以包含密钥派生,这是一种不适用于 Kyber 的方法。

答: 暂无答案