รักษาความปลอดภัยโทเค็นแบบสุ่มใน Node.js


273

ในคำถามนี้ Erik จำเป็นต้องสร้างโทเค็นแบบสุ่มที่ปลอดภัยใน Node.js มีวิธีcrypto.randomBytesที่สร้างบัฟเฟอร์แบบสุ่ม อย่างไรก็ตาม base64 เข้ารหัสในโหนดไม่ URL ปลอดภัยก็มี/และ+แทนและ- _ดังนั้นวิธีที่ง่ายที่สุดในการสร้างโทเค็นที่ฉันพบคือ

require('crypto').randomBytes(48, function(ex, buf) {
    token = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
});

มีวิธีที่สง่างามกว่านี้ไหม?


รหัสที่เหลือคืออะไร?
Lion789

3
ไม่จำเป็นต้องมีอะไรอีกแล้ว คุณต้องการพักผ่อนอะไร
Hubert OG

ไม่เป็นไรฉันจะทำให้มันทำงานไม่แน่ใจว่าคุณจะขว้างมันอย่างไร แต่ก็เข้าใจแนวคิดได้ดีขึ้น
Lion789

1
หน้าด้านปลั๊กอินตัวเองฉันสร้างแพคเกจ NPM อีก: tokgen คุณสามารถระบุตัวละครที่ได้รับอนุญาตโดยใช้ไวยากรณ์ช่วงคล้ายกับคลาสของตัวละครในการแสดงออกปกติ ( 'a-zA-Z0-9_-')
Max Truxa

1
สิ่งนี้อาจสะดวกสำหรับทุกคนที่ต้องการความยาวสตริงที่ระบุ 3 / 4th's คือการจัดการการแปลงฐาน / * ส่งคืนสตริงที่เข้ารหัส base64 ของความยาว * / ฟังก์ชัน randomString (ความยาว) {return crypto.randomBytes (length * 3/4) .toString ('base64'); } ใช้งานได้ดีสำหรับฐานข้อมูลเหล่านั้นด้วยการ จำกัด จำนวนอักขระเหล่านั้น
TheUnknownGeek

คำตอบ:


353

ลองcrypto.randomBytes () :

require('crypto').randomBytes(48, function(err, buffer) {
  var token = buffer.toString('hex');
});

การเข้ารหัส 'hex' ทำงานในโหนด v0.6.x หรือใหม่กว่า


3
ดูเหมือนว่าจะดีขึ้นขอบคุณ! แม้ว่าการเข้ารหัส 'base64-url' จะดีมาก
Hubert OG

2
ขอบคุณสำหรับเคล็ดลับ แต่ฉันคิดว่า OP ต้องการเพียง RFC 3548 มาตรา 4 มาตรฐาน "การเข้ารหัส 64 ฐานด้วย URL และชื่อไฟล์ที่ปลอดภัยอักษร" IMO การแทนที่ตัวละครคือ "สง่างามพอ"
natevw

8
หากคุณกำลังมองหาข้างต้นเป็นทุบตีหนึ่งซับคุณสามารถทำได้node -e "require('crypto').randomBytes(48, function(ex, buf) { console.log(buf.toString('hex')) });"
Dmitry Minkovsky

24
และคุณสามารถทำได้buf.toString('base64')เพื่อรับหมายเลขที่เข้ารหัสด้วย Base64
Dmitry Minkovsky

1
ดูผู้ตอบคำถามด้านล่างนี้สำหรับการเข้ารหัสฐาน 64ด้วย URL และตัวอักษรที่ปลอดภัยชื่อไฟล์
Yves M.

232

ตัวเลือกแบบซิงโครนัสในกรณีที่คุณไม่ใช่ผู้เชี่ยวชาญ JS อย่างฉัน ต้องใช้เวลาในการเข้าถึงตัวแปรฟังก์ชั่นอินไลน์

var token = crypto.randomBytes(64).toString('hex');

7
นอกจากนี้ในกรณีที่คุณไม่ต้องการมีทุกอย่างซ้อนกัน ขอบคุณ!
Michael Ozeryansky

2
ขณะนี้ใช้งานได้ดีโปรดทราบว่าในกรณีส่วนใหญ่คุณจะต้องการให้ตัวเลือก async แสดงในคำตอบของ thejh
Triforcey

1
const generateToken = (): Promise<string> => new Promise(resolve => randomBytes(48, (err, buffer) => resolve(buffer.toString('hex'))));
yantrab

1
@Triforcey คุณสามารถอธิบายได้ว่าทำไมคุณถึงต้องการตัวเลือก async?
มัส

2
@thomas ข้อมูลแบบสุ่มอาจใช้เวลาสักครู่ในการคำนวณขึ้นอยู่กับฮาร์ดแวร์ ในบางกรณีหากคอมพิวเตอร์ไม่มีข้อมูลแบบสุ่มมันก็แค่คืนค่าบางอย่างเข้าที่ อย่างไรก็ตามในกรณีอื่น ๆ อาจเป็นไปได้ที่คอมพิวเตอร์จะชะลอการส่งคืนข้อมูลแบบสุ่ม (ซึ่งจริงๆแล้วเป็นสิ่งที่คุณต้องการ) ทำให้เกิดการโทรช้า
Triforcey

80

0. การใช้ไลบรารีของบุคคลที่สาม nanoid [ใหม่!]

เครื่องมือสร้างสตริง ID ที่ไม่ซ้ำใครและปลอดภัยและเป็นมิตรกับ URL สำหรับ JavaScript

https://github.com/ai/nanoid

import { nanoid } from "nanoid";
const id = nanoid(48);


1. การเข้ารหัสฐาน 64 ด้วย URL และตัวอักษรที่ปลอดภัยของชื่อไฟล์

หน้า 7 ของ RCF 4648อธิบายวิธีเข้ารหัสในฐาน 64 พร้อมความปลอดภัยของ URL คุณสามารถใช้ไลบรารีที่มีอยู่เช่นbase64urlเพื่อทำงาน

ฟังก์ชั่นจะเป็น:

var crypto = require('crypto');
var base64url = require('base64url');

/** Sync */
function randomStringAsBase64Url(size) {
  return base64url(crypto.randomBytes(size));
}

ตัวอย่างการใช้งาน:

randomStringAsBase64Url(20);
// Returns 'AXSGpLVjne_f7w5Xg-fWdoBwbfs' which is 27 characters length.

โปรดทราบว่าความยาวสตริงที่ส่งคืนจะไม่ตรงกับอาร์กิวเมนต์ขนาด (ขนาด! = ความยาวสุดท้าย)


2. Crypto สุ่มค่าจากชุดอักขระที่ จำกัด

ระวังด้วยวิธีนี้สตริงสุ่มที่สร้างขึ้นจะไม่กระจายอย่างสม่ำเสมอ

คุณยังสามารถสร้างสตริงแบบสุ่มที่แข็งแกร่งจากชุดอักขระที่ จำกัด เช่นนั้น:

var crypto = require('crypto');

/** Sync */
function randomString(length, chars) {
  if (!chars) {
    throw new Error('Argument \'chars\' is undefined');
  }

  var charsLength = chars.length;
  if (charsLength > 256) {
    throw new Error('Argument \'chars\' should not have more than 256 characters'
      + ', otherwise unpredictability will be broken');
  }

  var randomBytes = crypto.randomBytes(length);
  var result = new Array(length);

  var cursor = 0;
  for (var i = 0; i < length; i++) {
    cursor += randomBytes[i];
    result[i] = chars[cursor % charsLength];
  }

  return result.join('');
}

/** Sync */
function randomAsciiString(length) {
  return randomString(length,
    'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
}

ตัวอย่างการใช้งาน:

randomAsciiString(20);
// Returns 'rmRptK5niTSey7NlDk5y' which is 20 characters length.

randomString(20, 'ABCDEFG');
// Returns 'CCBAAGDGBBEGBDBECDCE' which is 20 characters length.

2
@Lexynux โซลูชัน 1 (การเข้ารหัส 64 ฐานด้วย URL และตัวอักษรที่ปลอดภัยของชื่อไฟล์) เนื่องจากเป็นโซลูชันที่แข็งแกร่งที่สุดในด้านความปลอดภัย โซลูชันนี้เข้ารหัสคีย์เท่านั้นและไม่รบกวนกระบวนการผลิตคีย์
Yves M.

ขอบคุณสำหรับการสนับสนุน. คุณมีตัวอย่างการทำงานใด ๆ ที่จะแบ่งปันกับชุมชนหรือไม่ มันจะได้รับการต้อนรับ?
alexventuraio

6
ระวังว่าสตริงสุ่มที่สร้างขึ้นจะไม่กระจายอย่างสม่ำเสมอ ตัวอย่างง่ายๆในการแสดงนี้คือสำหรับชุดอักขระที่มีความยาว 255 และความยาวสตริงที่ 1 โอกาสของอักขระแรกที่ปรากฏขึ้นจะสูงเป็นสองเท่า
Florian Wendelborn

@Dodekeract ใช่คุณกำลังพูดถึงวิธีแก้ปัญหา 2 .. นั่นเป็นสาเหตุที่วิธีที่ 1 แข็งแกร่งกว่านี้
Yves M.

ฉันได้เพิ่มห้องสมุดของบุคคลที่สาม nanoid ในการตอบสนองของฉันgithub.com/ai/nanoid
Yves M.

13

วิธีที่ถูกต้องทันสมัยในการทำแบบอะซิงโครนัสโดยใช้มาตรฐาน ES 2016 ของ async และรอ (ณ โหนด 7) จะเป็นดังต่อไปนี้:

const crypto = require('crypto');

function generateToken({ stringBase = 'base64', byteLength = 48 } = {}) {
  return new Promise((resolve, reject) => {
    crypto.randomBytes(byteLength, (err, buffer) => {
      if (err) {
        reject(err);
      } else {
        resolve(buffer.toString(stringBase));
      }
    });
  });
}

async function handler(req, res) {
   // default token length
   const newToken = await generateToken();
   console.log('newToken', newToken);

   // pass in parameters - adjust byte length
   const shortToken = await generateToken({byteLength: 20});
   console.log('newToken', shortToken);
}

สิ่งนี้ได้ผลนอกกรอบในโหนด 7 โดยไม่มีการแปลง Babel


ฉันได้อัปเดตตัวอย่างนี้เพื่อรวมวิธีการใหม่กว่าในการส่งพารามิเตอร์ที่มีชื่อตามที่อธิบายไว้ที่นี่: 2ality.com/2011/11/keyword-parameters.html
real_ate

7

URL แบบสุ่มและสตริงชื่อไฟล์ปลอดภัย (1 ไลเนอร์)

Crypto.randomBytes(48).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');

คำตอบที่ยอดเยี่ยมในเรื่องของความเรียบง่าย! เพิ่งทราบว่าสามารถหยุดเหตุการณ์ลูปในลักษณะไม่แน่นอน (เฉพาะที่เกี่ยวข้องหากมีการใช้บ่อยในระบบที่มีความไวต่อเวลาโหลดค่อนข้าง) มิฉะนั้นทำสิ่งเดียวกัน แต่ใช้ async รุ่น randomBytes ดูnodejs.org/api/…
Alec Thilenius

6

เช็คเอาท์:

var crypto = require('crypto');
crypto.randomBytes(Math.ceil(length/2)).toString('hex').slice(0,length);

ดี! วิธีการแก้ปัญหาที่ underrated อย่างแน่นอน จะดีถ้าคุณเปลี่ยนชื่อ "ระยะเวลา" กับ "desiredLength" และเริ่มต้นด้วยค่าก่อนที่จะใช้ :)
Florian บลัม

สำหรับทุกคนที่สงสัยว่าการโทรceilและการsliceโทรเป็นสิ่งจำเป็นสำหรับความยาวที่ต้องการซึ่งเป็นเลขคี่ สำหรับความยาวพวกเขาจะไม่เปลี่ยนแปลงอะไรเลย
เซท

6

ด้วย async / รอคอยและpromisification

const crypto = require('crypto')
const randomBytes = Util.promisify(crypto.randomBytes)
const plain = (await randomBytes(24)).toString('base64').replace(/\W/g, '')

สร้างสิ่งที่คล้ายกับ VjocVHdFiz5vGHnlnwqJKN0NdeHcz8eM


4

ดูreal_atesวิธี ES2016 มันถูกต้องมากขึ้น

ECMAScript 2016 (ES7) วิธี

import crypto from 'crypto';

function spawnTokenBuf() {
    return function(callback) {
        crypto.randomBytes(48, callback);
    };
}

async function() {
    console.log((await spawnTokenBuf()).toString('base64'));
};

วิธีการกำเนิด / ผลผลิต

var crypto = require('crypto');
var co = require('co');

function spawnTokenBuf() {
    return function(callback) {
        crypto.randomBytes(48, callback);
    };
}

co(function* () {
    console.log((yield spawnTokenBuf()).toString('base64'));
});

@Jeffpowrs แน่นอน Javascript กำลังอัพเกรด :) สัญญาการค้นหาและเครื่องกำเนิดไฟฟ้า!
K - ความเป็นพิษใน SO กำลังเพิ่มขึ้น

ลองรอตัวจัดการสัญญาอีกครั้งของ ECMA7
Jain

ฉันคิดว่าคุณควรทำให้ ES 2016 เป็นตัวอย่างแรกของเรื่องนี้เนื่องจากมันกำลังเคลื่อนไปสู่ ​​"วิธีที่ถูกต้องที่จะทำ" ในกรณีส่วนใหญ่
real_ate

ฉันได้เพิ่มคำตอบของฉันเองด้านล่างที่เฉพาะเจาะจงกับโหนด (ใช้การใช้แทนการนำเข้า) มีเหตุผลบางประการที่ทำให้คุณใช้การนำเข้าหรือไม่ คุณวิ่งบาเบลหรือไม่?
real_ate

@real_ate จริง ๆ แล้วฉันเป็นฉันฉันได้กลับไปใช้ CommonJS จนกว่าการนำเข้าได้รับการสนับสนุนอย่างเป็นทางการ
K - ความเป็นพิษใน SO กำลังเพิ่มขึ้น


2

โมดูลnpm anyidจัดเตรียม API ที่ยืดหยุ่นเพื่อสร้าง ID สตริง / รหัสสตริงประเภทต่างๆ

ในการสร้างสตริงแบบสุ่มใน A-Za-z0-9 โดยใช้ 48 ไบต์แบบสุ่ม:

const id = anyid().encode('Aa0').bits(48 * 8).random().id();
// G4NtiI9OYbSgVl3EAkkoxHKyxBAWzcTI7aH13yIUNggIaNqPQoSS7SpcalIqX0qGZ

ในการสร้างตัวอักษรความยาวคงที่สตริงเท่านั้นที่เต็มไปด้วยไบต์สุ่ม

const id = anyid().encode('Aa').length(20).random().id();
// qgQBBtDwGMuFHXeoVLpt

ภายในจะใช้crypto.randomBytes()ในการสร้างแบบสุ่ม


1

นี่คือเวอร์ชัน async ที่ใช้คำต่อคำจากคำตอบของ @Yves M. ด้านบน

var crypto = require('crypto');

function createCryptoString(length, chars) { // returns a promise which renders a crypto string

    if (!chars) { // provide default dictionary of chars if not supplied

        chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    }

    return new Promise(function(resolve, reject) {

        var charsLength = chars.length;
        if (charsLength > 256) {
            reject('parm chars length greater than 256 characters' +
                        ' masks desired key unpredictability');
        }

        var randomBytes = crypto.randomBytes(length);

        var result = new Array(length);

        var cursor = 0;
        for (var i = 0; i < length; i++) {
            cursor += randomBytes[i];
            result[i] = chars[cursor % charsLength];
        }

        resolve(result.join(''));
    });
}

// --- now generate crypto string async using promise --- /

var wantStringThisLength = 64; // will generate 64 chars of crypto secure string

createCryptoString(wantStringThisLength)
.then(function(newCryptoString) {

    console.log(newCryptoString); // answer here

}).catch(function(err) {

    console.error(err);
});

1

ฟังก์ชั่นง่าย ๆ ที่จะทำให้คุณได้รับโทเค็นที่ปลอดภัยและมีการเข้ารหัส base64! มันเป็นการรวมกันของ 2 คำตอบจากด้านบน

const randomToken = () => {
    crypto.randomBytes(64).toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.