Uint8array nodejs

Đã từ lâu, Javascript ra đời giúp các nhà phát triển có thể xây dựng các trang web với nhiều logic phức tạp, thực hiện đủ thứ xử lý ở phía máy khách ngay, không cần nhờ máy chủ. Tuy nhiên, số lượng các thư viện Javascript cung cấp tính năng mã hóa/giải mã khá hạn chế và cũng dẫn đến các ứng dụng web cung cấp khả năng mã hóa nội dung ngay trên trình duyệt không phổ biến. Ít nhất là cho đến thời gian gần đây, với sự xuất hiện của Web Crypto API

Đặc tính, tính đơn luồng, bất đồng bộ và hiệu năng thấp (so với các ngôn ngữ như C, Rust), tất cả đều làm cho Javascript không phù hợp với các thuật toán liên quan đến mã hóa/giải mã. Bản thân Javascript cũng thiếu tính năng tạo dữ liệu giả ngẫu nhiên đủ an toàn (CSPRNG, hay viết tắt của trình tạo số giả ngẫu nhiên bảo mật bằng mật mã). Vì vậy, Web Crypto API ra đời nhằm cung cấp các tính năng mã hóa/giải mã an toàn ngay trên trình duyệt

Tại sao cần WebCrypto API?

Trong thế giới web, người dùng thường phải "tin tưởng máy chủ". Người dùng nhập mật khẩu đăng ký tài khoản, khả năng đó cũng là mật khẩu chúng ta sử dụng cho email và nhiều tài khoản trên mạng khác (vì chắc nhớ nhiều mà), và sau đó phải hy vọng máy chủ của trang web sẽ bảo vệ mật khẩu đủ mức . Người dùng cũng phải mong muốn các nội dung khác bạn lưu vào máy chủ sau đó như hộp thư đến, số thẻ tín dụng,. được bảo mật và không có vụ rò rỉ dữ liệu nào xảy ra

Gần đây quyền riêng tư đang là vấn đề nóng. Nhiều trang web ngày càng sử dụng dữ liệu của người dùng nhiều hơn cho mục đích riêng. Gmail sử dụng email nội dung phục vụ cho quảng cáo được cá nhân hóa, hay Evernote tuyên bố sẽ cho nhân viên đọc các đoạn trích ngắn trong ghi chú của người dùng để phục vụ cho máy học. Chưa kể, nếu chủ nhân trang web tuyên bố sẽ tôn trọng quyền riêng tư của người dùng, thì trường hợp éo le như chủ web đó bị đe dọa hay chính phủ yêu cầu, dữ liệu người dùng vẫn bị xâm phạm như thường, tiêu

Vì những lý do trên, nhiều người đã muốn tạo các ứng dụng Web theo kiểu Zero-Knowledge hoặc có khả năng mã hóa phía client (mã hóa phía máy khách) hoặc mã hóa đầu cuối (mã hóa đầu cuối). Máy chủ phía sau của các ứng dụng Web này chỉ được sử dụng để lưu hoặc truyền tải dữ liệu đã được mã hóa. Key giải mã không bao giờ được gửi đến máy chủ và quá trình mã hóa/giải mã được thực hiện hoàn toàn ngoại tuyến ở phía máy khách. Như vậy các máy chủ này hoàn toàn không có kiến ​​thức. Một số trang Web tiên phong theo mô hình này điển hình như bitwarden. com, ghi chú chuẩn. org hay protonmail. com. Cá biệt có dịch vụ web tận dụng mã hóa phía máy khách để bảo vệ máy chủ chính của họ khỏi những vấn đề liên quan đến luật, ví dụ như trang tải lên tệp có mã hóa phía máy khách lớn. nz

Nhưng nên nhớ, dù thế nào đi chăng nữa, bạn vẫn phải tin tưởng vào máy chủ, ít nhất là để cung cấp cho bạn các đoạn mã phía máy khách (như javascript) một cách an toàn. Chỉ có điều máy chủ không cần biết quá nhiều

Cách sử dụng

Dưới đây mình chỉ hướng dẫn cách sử dụng cơ bản cho mục đích giới thiệu và rất có thể nhiều chỗ không chính xác và không an toàn. Nên nhớ rằng nếu bạn muốn làm một trang Web tận dụng Web Crypto API với yêu cầu bảo mật cao, bạn phải cẩn thận hơn là rất nhiều, và có đủ hiểu biết về một thông tin đầy đủ. Một sai lầm nhỏ sẽ khiến việc mã hóa trở nên mất hiệu quả hoặc hoàn toàn vô hiệu

Để hiểu bài viết rõ ràng nhất, bạn cần biết một chút về javascript như lời hứa và async/await, các kiến ​​thức về

const digest = crypto.subtle.digest(algorithm, data);
8 và các góc nhìn của nó
const digest = crypto.subtle.digest(algorithm, data);
9. Ngoài ra, để giảm thiểu thời gian, các ví dụ trong bài được thực hiện bởi thư viện Vue. js

Ngoài ra, vì công nghệ Web luôn thay đổi rất nhanh, để có những thông tin chính xác nhất, bạn nên tham khảo tài liệu của MDN về Web Crypto API

Sinh ngẫu nhiên với const getKeyMaterial = async (passwordString) => { const passwordBuffer = new TextEncoder().encode(passwordString); return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]); } 0

Giúp tạo dữ liệu giả ngẫu nhiên đủ an toàn để sử dụng với mã hóa (CSPRNG). Thường được dùng để tạo ra IV, muối,

// Tạo typed array với 16 byte
let byte = new Uint8Array(16);
// Lấp đầy typed array trên với random value
crypto.getRandomValues(byte);

Nhớ rằng máy tính của chúng ta là. máy tính, nó không hề có khái niệm ngẫu nhiên. Hàm ngẫu nhiên của các ngôn ngữ lập trình chỉ tạo ra dữ liệu "trông có vẻ" ngẫu nhiên bằng cách phụ thuộc vào trạng thái bên trong bất kỳ trạng thái nào, nên được gọi là giả ngẫu nhiên. Hàm ngẫu nhiên bình thường này (PRNG) cho hiệu năng cao nhưng dễ đoán các giá trị ngẫu nhiên tiếp theo, nên chỉ phù hợp với mục đích của hệ thống kê

CSPRNG only other PRNG at two point

  • Không thể phân biệt giá trị được tạo bởi CSPRNG với giá trị thực ngẫu nhiên (chuyển động của bầu khí thải thực sự có giới hạn)
  • Cannot đoán các giá trị ngẫu nhiên tiếp theo (giả sử trạng thái bên trong không được tiết lộ)

Điều đó khiến cho CSPRNG đủ an toàn cho các mục đích mã hóa

hash by const getKeyMaterial = async (passwordString) => { const passwordBuffer = new TextEncoder().encode(passwordString); return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]); } 1

Trợ giúp "băm" hay băm bất kỳ dữ liệu nào bằng một loại hàm băm nhanh. Show it only support. SHA-1, SHA-256, SHA-384, SHA-512

const digest = crypto.subtle.digest(algorithm, data);
  • const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    2. TOÁN TÊN, MỘT TRONG 4 LOẠI.
    const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    3,
    const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    4,
    const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    5,
    const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    6
  • const getKeyMaterial = async (passwordString) => {
      const passwordBuffer = new TextEncoder().encode(passwordString);
      return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
    }
    
    7 must be
    const digest = crypto.subtle.digest(algorithm, data);
    
    8 or an frames of
    const digest = crypto.subtle.digest(algorithm, data);
    
    8, as typed array
    const digest = crypto.subtle.digest(algorithm, data);
    
    40 nhìn chẳng hạn
  • const digest = crypto.subtle.digest(algorithm, data);
    
    41 sẽ là một lời hứa, điền đầy đủ với một
    const digest = crypto.subtle.digest(algorithm, data);
    
    8 chứa chuỗi kết quả sau khi bị băm

Lưu ý rằng, sử dụng hàm băm nhanh để xác thực tính toàn vẹn của dữ liệu. Don't bao giờ sử dụng hàm hash nhanh như ở trên để bảo vệ hoặc tạo khóa cho mật khẩu

Băm mật khẩu với PBKDF2

PBKDF2 là hàm giúp sinh khóa từ mật khẩu một cách an toàn (hàm dẫn xuất khóa). Khóa này có thể được sử dụng để làm khóa trong thuật toán mã hóa (như AES)

Tuy mục đích ban đầu của PBKDF2 là tạo khóa, PBKDF2 cũng được coi là đủ cả ngày để tạo mật khẩu băm hay mật khẩu tiêu hóa (băm mật khẩu). PBKDF2 được thiết kế để làm chậm, điều này giúp tấn công brute force để tìm khóa được sinh ra qua hàm PBKDF2 khó hơn rất nhiều

Tạo hàm băm cho mật khẩu với PBKDF2 cần có các yếu tố sau

  • Mật khẩu đầu vào để tạo hàm băm
  • Muối là giá trị độc nhất (và tốt hơn nữa là ngẫu nhiên). Nó để bảo vệ mật khẩu khỏi cuộc tấn công bảng cầu vồng và không cần giữ bí mật. Luôn sử dụng muối mới sau mỗi lần băm (mình sẽ chọn độ dài là 32 byte, tức là 256 bit)
  • lặp đi lặp lại. number of the loop, more than, too the hash too slow (mình tạm thời là 100000)
  • độ dài khóa. Độ dài của khóa PBKDF2 được sinh ra theo bit (mình xin tạm thời sử dụng là 160)

Chúng ta sẽ sử dụng

const digest = crypto.subtle.digest(algorithm, data);
43 để tạo mã mũ trùm đầu, nhưng có một số công việc phải làm trước

Chuyển chuỗi mật khẩu cần tạo mã băm sang dạng đối tượng const digest = crypto.subtle.digest(algorithm, data); 44

const digest = crypto.subtle.digest(algorithm, data);
43 hơi phiền là không chấp nhận mật khẩu dưới dạng chuỗi thông thường, chỉ chấp nhận nhận mật khẩu ở dạng đối tượng
const digest = crypto.subtle.digest(algorithm, data);
44. Do đó ta phải chuyển đổi mật khẩu định danh thành một đối tượng
const digest = crypto.subtle.digest(algorithm, data);
44 trước bằng
const digest = crypto.subtle.digest(algorithm, data);
48. Nhưng lại tiếp tục, yêu cầu mật khẩu
const digest = crypto.subtle.digest(algorithm, data);
48 phải ở dạng
const digest = crypto.subtle.digest(algorithm, data);
8
Uint8array nodejs
( Túm lại là như sau.

const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}

tạo muối

Use

const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}
0 as at on section

const digest = crypto.subtle.digest(algorithm, data);
4

Tiến hành tạo mã hash

Sau khi đã có 2 thành phần trên, chúng ta tiến hành tạo mã băm qua

const digest = crypto.subtle.digest(algorithm, data);
43

const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}
9

Reconnection

Gắn kết 3 hàm trên lại với nhau

// Tạo typed array với 16 byte
let byte = new Uint8Array(16);
// Lấp đầy typed array trên với random value
crypto.getRandomValues(byte);
0

Hãy nhớ rằng

const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}
93 sẽ trả về một
const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}
94 mà sau một khoảng thời gian sẽ trả về một
const digest = crypto.subtle.digest(algorithm, data);
8 chứa kết quả bẩn

Bạn nên tham khảo ví dụ bên dưới để hiểu rõ, đồng thời có thể tham khảo thêm tại https. //Trung bình. com/coinmonks/fun-times-with-webcrypto-part-1-pbkdf2-815b1c978c9d

Như đã nói ở trên, PBKDF2 cũng được sử dụng để tạo ra khóa từ mật khẩu cho thuật toán như AES. Phần dưới đây sẽ đi vào chi tiết hơn

Mã hóa đối xứng (và giải mã) với AES-GCM

Hiểu ngắn gọn, mã hóa đối xứng là kiểu mã hóa mà bạn sử dụng cùng một khóa (phím) để mã hóa và giải mã dữ liệu. Còn AES là thuật toán mã hóa đối với rất an toàn và mạnh mẽ hiện nay, kể cả cho mục tiêu chính phủ hay quân sự

AES thường sử dụng key với độ dài 256 bit. Nói đơn giản thì không ai có thể tấn công vét cạn (brute force) đoạn khóa này với công nghệ hiện tại. Để tạo đoạn khóa dài 256 bit dành cho AES, người ta có thể chọn sử dụng CSPRNG để sinh ra đoạn khóa bất kỳ, hoặc sinh khóa từ mật khẩu với một hàm sinh khóa (hàm dẫn xuất khóa) như PBKDF2

AES-GCM là kiểu thuật toán AES thông dụng nhất, cho hiệu năng giải mã tốt hơn với CPU đa nhân, đồng thời đảm bảo tính xác thực của dữ liệu, tức thì biết dữ liệu còn nguyên trạng khi giải mã hay không

Để tránh việc sử dụng cùng một khóa mã hóa ra các dữ liệu giống nhau cho các dữ liệu đã mã hóa giống nhau, người ta sử dụng thêm một giá trị gọi là IV (Vectơ khởi tạo). IV là đoạn giá trị độc nhất (ngẫu nhiên thì càng tốt) khá giống muối. Ngoài ra đặc biệt với AES-GCM, sử dụng IV/nonce độc ​​nhất cho mỗi lần mã hóa là bắt buộc và vô cùng quan trọng, tốt nhất là trong trường hợp bạn sử dụng cùng một phím để mã hóa nhiều dữ liệu khác nhau. Trong bài viết này mình xin tạm thời chọn độ dài giá trị IV/nonce là 12 byte (96 bit)

Dưới đây mình sẽ chọn mã hóa bằng cách sinh khóa từ mật khẩu, đồng thời dữ liệu được chọn để mã hóa chỉ là đoạn chữ UTF-8

Create key from password

Sử dụng PBKDF2. Gần giống như mái che với phần băm mật khẩu bằng PBKDF2 ở phía trên, chỉ khác một chút ở nơi sử dụng

const getKeyMaterial = async (passwordString) => {
  const passwordBuffer = new TextEncoder().encode(passwordString);
  return await crypto.subtle.importKey("raw", passwordBuffer, "PBKDF2", false, ["deriveBits"]);
}
96 thay cho
const digest = crypto.subtle.digest(algorithm, data);
43