在nodejs中创建安全令牌

dsekswqp  于 2022-12-18  发布在  Node.js
关注(0)|答案(1)|浏览(171)

bounty将在2天后过期。回答此问题可获得+50的声誉奖励。user2675468正在寻找来自声誉良好来源的答案

我有一个json,日期为:

{
  password: 'mySecretPwd',
  validUntil: dayjs().add(1, 'hour').toDate(),
  userId: '29038492132'
}

现在我想创建一个类似魔术链接(myUrl/)的东西。当用户点击链接时,它必须打开一个网页,用户必须将密码写入一个文本字段。提交后,它发送一个包含用户输入和链接中的令牌的请求。然后服务器检查输入是否等于令牌中的用户输入,并验证用户是否有效。
现在我的问题是如何实现它。我的代码看起来像这样:

const crypto = require('crypto')

const algorithm = 'aes-256-ctr'
const secretKey = process.env.JWT_TEST_RESULT_SECRET

export const encrypt = (text) => {
  const iv = crypto.randomBytes(16)

  const cipher = crypto.createCipheriv(algorithm, secretKey, iv)

  const encrypted = Buffer.concat([cipher.update(text), cipher.final()])

  return {
    iv: iv.toString('hex'),
    content: encrypted.toString('hex')
  }
}

因为我需要iv来解码,所以我会创建如下链接:myUrl/<content>_<iv>
这是不是一个好的和安全的方法?
我希望你能提供一些意见谢谢

r3i60tvu

r3i60tvu1#

我假设您希望在项目中实现2fa。
对于2fa身份验证,您将在用户尝试登录时重置用户密码,然后向他们发送一个令牌作为响应,并将其保存在项目内存中(在变量或状态管理中)。
为了实现这种方式(不存储令牌),最好在他们要求登录后给他们发邮件,(你可以使用SendGrid)他们可以点击并插入密码的链接,当然沿着他们的新密码(我建议你考虑只使用带有静态密码的令牌或只向他们发送密码)。
尊敬用户,您的新密码是:NEW_PASSWORD,请在下面的链接中输入您的密码:您的前端.com/插入密码/?令牌=新令牌&用户名=用户名(我建议您将令牌的到期时间设置为2分钟)
在前端,我们在nuxt.js中的查询中查找令牌和用户名,例如,您可以通过编写以下内容访问查询中的项目:.$route.query.YOUR_DESIRED_ITEM_IN_QUERY。
如果我们在查询中得到了我们想要的项目,我们要求用户插入他们的密码,否则我们将他们重定向到登录页面。
在他们插入密码后,我们将用户名、密码和令牌发送到后端进行验证。
用于验证密码并对其进行散列:
我会考虑使用bycrypt。因为它很容易使用,你想过期你的密码每1小时(我建议2分钟)。没有必要更复杂的东西。
但是如果您希望为用户提供一个静态密码(并且您希望它尽可能安全),并且除非用户自己更新它,否则不会随时间而更改,那么我建议您不要在数据库中存储密码(即使是它的散列版本)。
为了达到这个目的,你必须用你的密码作为密钥来散列来自用户的不可改变的数据(如ID),并将散列数据存储在数据库中,这样,即使你存储的散列数据被解密,攻击者也不会访问密码,相反,他们会获得ID。
第一步:将您的密码转换为32进制。(您将使用您的密码作为您的密钥稍后),

function convertPassword (secretKey, password) {
    
        if(!password.length || password.length > 32){
            return false;
        }
    
        if(secretKey.length !== 32) {
            return false;
        }
    
        return`${secretKey.split('').splice(password.length).join('')}${password}`;
        
    }

步骤2:加密

import {randomBytes, createCipheriv} from "crypto";    
function encrypt (textToEncrypt, secretKey) {
    
        if(secretKey.length !== 32){
            return false
        }
    
        const cipher = createCipheriv('aes-256-ctr', secretKey, randomBytes);
    
        const encrypted = Buffer.concat([cipher.update(textToEncrypt), cipher.final()]);
    
        return  `${randomBytes.toString('hex')}:${encrypted.toString('hex')}`
    
    }

在这一步中,你将用户ID作为“textToEncrypt”传递,并将转换后的密码作为“secretKey”传递。
最后一步:解密

import {createDecipheriv} from "crypto";
    function decrypt (encryptedText, secretKey, id) => {
        
        if(secretKey.length !== 32){
            return false
        }

        const splicedHash = encryptedText.split(':')
    
        const hashIv = splicedHash[0]
        const hashContent = splicedHash[1]
    
        const decipher = createDecipheriv('aes-256-ctr', secretKey, Buffer.from(hashIv, 'hex'));
    
        const decrypted = Buffer.concat([decipher.update(Buffer.from(hashContent, 'hex')), decipher.final()]);
    
        const result = decrypted.toString();
        
        return result === id;
    }


encryptedText是你已经存储到你的数据库中的文本,secretKey是用户已经插入的密码,并且你已经运行了你的转换密码函数,id是你已经用于加密的用户id.如果密码有效,这个函数返回true,如果密码无效,返回false.
PS:如果你的项目不需要那么多的安全性,我仍然建议你使用bycrypt

相关问题