☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Bảo mật (Security)

Encryption (Mã hóa) and Hashing (Băm)

Bản dịch từ docs.nestjs.com/security/encryption-and-hashing

Encryption (mã hóa) là quá trình chuyển đổi thông tin. Quá trình này biến đổi biểu diễn gốc của thông tin, gọi là plaintext (văn bản gốc), thành một dạng khác gọi là ciphertext (văn bản mã hóa). Về lý thuyết, chỉ những bên được cấp quyền mới có thể giải mã ciphertext ngược lại thành plaintext để lấy được thông tin gốc. Encryption bản thân nó không ngăn việc thông tin bị chặn lại, nhưng nó khiến nội dung bị chặn trở nên vô nghĩa với người chặn. Encryption là một hàm hai chiều: cái gì được mã hóa thì có thể được giải mã với key đúng.

Hashing (băm) là quá trình chuyển một key cho trước thành một giá trị khác. Một hash function (hàm băm) được dùng để sinh ra giá trị mới theo một thuật toán toán học. Sau khi đã băm, về lý thuyết không thể đi ngược từ output về lại input.

Encryption (Mã hóa)

Node.js cung cấp sẵn module crypto mà bạn có thể dùng để mã hóa và giải mã string, number, buffer, stream, và nhiều hơn nữa. Nest bản thân không cung cấp thêm package nào bọc trên module này, để tránh tạo ra các lớp trừu tượng không cần thiết.

Ví dụ, hãy dùng thuật toán AES (Advanced Encryption System) với chế độ mã hóa CTR 'aes-256-ctr'.

import { createCipheriv, randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const iv = randomBytes(16);
const password = 'Password used to generate key';

// The key length is dependent on the algorithm.
// In this case for aes256, it is 32 bytes.
const key = (await promisify(scrypt)(password, 'salt', 32)) as Buffer;
const cipher = createCipheriv('aes-256-ctr', key, iv);

const textToEncrypt = 'Nest';
const encryptedText = Buffer.concat([
  cipher.update(textToEncrypt),
  cipher.final(),
]);

Giờ để giải mã giá trị encryptedText:

import { createDecipheriv } from 'node:crypto';

const decipher = createDecipheriv('aes-256-ctr', key, iv);
const decryptedText = Buffer.concat([
  decipher.update(encryptedText),
  decipher.final(),
]);

Ghi chú người dịch So với PHP (openssl_encrypt/openssl_decrypt) hay Go (package crypto/aes kết hợp cipher.NewCTR), cách dùng ở đây tương tự: bạn cần một key đúng độ dài thuật toán yêu cầu (AES-256 cần key 32 byte) và một IV (initialization vector) ngẫu nhiên riêng cho mỗi lần mã hóa. scrypt ở đây đóng vai trò là hàm dẫn xuất key (key derivation function) từ password, giúp bạn không dùng trực tiếp password làm key — điều này quan trọng vì password do người dùng đặt thường không đủ entropy (độ ngẫu nhiên) để làm key mã hóa an toàn.

Hashing (Băm)

Để băm dữ liệu, chúng ta khuyến nghị dùng package bcrypt hoặc argon2. Nest bản thân không cung cấp thêm wrapper nào bọc trên các module này, để tránh tạo ra các lớp trừu tượng không cần thiết (giữ đường học ngắn gọn).

Ví dụ, hãy dùng bcrypt để băm một password ngẫu nhiên.

Đầu tiên, cài package cần thiết:

$ npm i bcrypt
$ npm i -D @types/bcrypt

Sau khi cài xong, bạn có thể dùng hàm hash như sau:

import bcrypt from 'bcrypt';

const saltOrRounds = 10;
const password = 'random_password';
const hash = await bcrypt.hash(password, saltOrRounds);

Để generate một salt, dùng hàm genSalt:

const salt = await bcrypt.genSalt();

Để so sánh/kiểm tra một password, dùng hàm compare:

const isMatch = await bcrypt.compare(password, hash);

Bạn có thể đọc thêm về các hàm khả dụng tại đây.

Ghi chú người dịch Điểm khác biệt quan trọng cần nhớ: encryption (mã hóa) là hai chiều — dùng cho dữ liệu bạn cần đọc lại nguyên bản sau này (ví dụ số thẻ, dữ liệu nhạy cảm cần hiển thị lại). Hashing (băm) là một chiều — chỉ dùng để so sánh, không bao giờ nên dùng để lưu thứ bạn cần lấy lại nguyên bản, điển hình nhất là password. Nhầm lẫn hai khái niệm này (ví dụ “mã hóa” password rồi “giải mã” để so sánh) là một lỗi bảo mật khá phổ biến ở dev mới.

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.