guest@websec: ~/websec/16-cryptography/th — zsh — 100×40 เขียนโดย SaKKo ENTH โค้ด:

guest@websec ~ % man 16-cryptography

16 · การเข้ารหัสและการปกป้องข้อมูล

บั๊กด้านการเข้ารหัสแทบไม่เคยเป็นเรื่องคณิตศาสตร์พัง แต่เป็นความสับสน: คิดว่า encoding คือการเข้ารหัส ใช้ hash แบบเร็วกับรหัสผ่าน ตัวเลขสุ่มที่เดาได้ คีย์ที่วางไว้ข้างข้อมูล

16.1 เครื่องมือสี่แบบที่ต่างกัน

เครื่องมือย้อนกลับได้?ต้องใช้คีย์?ใช้สำหรับ
Encoding (Base64, hex, URL-encoding)ได้ ใครก็ได้ไม่ทำให้ไบต์ส่งต่อได้อย่างปลอดภัย ไม่ใช่การรักษาความลับ
Hashing (SHA-256; bcrypt/Argon2 for passwords)ไม่ไม่ (มี salt สำหรับรหัสผ่าน)ลายนิ้วมือข้อมูล ตรวจความถูกต้อง เก็บรหัสผ่าน (เฉพาะ hash แบบช้า)
Encryption (AES-256-GCM, ChaCha20-Poly1305)ได้ ถ้ามีคีย์ใช่รักษาความลับของข้อมูลขณะจัดเก็บหรือรับส่ง
Signing / MAC (HMAC-SHA256, Ed25519)ข้อมูลยังอ่านได้ใช่พิสูจน์ว่าข้อมูลไม่ถูกแก้และมาจากคุณ (token, คุกกี้, webhook)
sim: ความลับเดียวกัน สี่วิธีWeb Crypto จริงในเบราว์เซอร์ของคุณ

"การเข้ารหัส" แบบ Base64

hash SHA-256

การเข้ารหัส AES-256-GCM

ลายเซ็น HMAC-SHA256

16.2 การปกป้องข้อมูลขณะจัดเก็บ

ข้อแรก อย่าเก็บสิ่งที่ไม่จำเป็น ส่วนที่เหลือ การเข้ารหัสดิสก์กันดิสก์ที่ถูกขโมยได้ แต่ใครที่ query ฐานข้อมูลได้ยังเห็นข้อความเปล่า การเข้ารหัสรายฟิลด์ ทำให้ dump, backup และ query ที่สอดรู้สอดเห็นได้แต่ตัวอักษรมั่วๆ

Rails มี Active Record Encryption แต่ต้องเปิดเอง: bin/rails db:encryption:init แล้ว encrypts :ssn ใช้ deterministic: true เฉพาะคอลัมน์ที่ต้องค้นหา
class Patient < ApplicationRecord
  encrypts :ssn                                   # non-deterministic (default, strongest)
  encrypts :email, deterministic: true, downcase: true   # queryable: where(email: …)
end

# bin/rails db:encryption:init → add the output to credentials:
# active_record_encryption:
#   primary_key: …   deterministic_key: …   key_derivation_salt: …

16.3 คีย์

  • เก็บคีย์ แยกจากข้อมูล: ใน credentials หรือ secret manager
  • คีย์หนึ่งตัวต่อหนึ่งงาน รั่วตัวเดียวจะได้ไม่เปิดทุกอย่าง
  • วางแผน การหมุนเวียนคีย์ ก่อนจะต้องใช้

16.4 ความสุ่ม

token ต้องเดาไม่ได้ rand กับ Math.random() มีไว้ทำเกม ถ้าได้ตัวอย่างมากพอ ผู้โจมตีทำนายตัวถัดไปได้ ให้ใช้ตัวสุ่มทางคริปโต

เดาได้ (ห้ามใช้)ทางคริปโต (ใช้)
Rubyrand, Random.new, Array#sampleSecureRandom.urlsafe_base64(32), SecureRandom.uuid
JavaScriptMath.random()crypto.getRandomValues(), crypto.randomUUID()
Pythonrandomsecrets.token_urlsafe(32)

ทำ token ให้ยาว (128 บิตขึ้นไป) รหัส 6 หลักต้องจำกัดอัตราอย่างเข้มงวด

16.5 Timing attack และการเปรียบเทียบแบบใช้เวลาคงที่

การเทียบสตริงปกติหยุดที่ตัวแรกที่ผิด การเดาที่ใกล้กว่าจึงช้ากว่านิดหน่อย วัดมากพอก็กู้ความลับได้ทีละตัว ให้ใช้ การเทียบแบบเวลาคงที่ กับความลับ

sim: token รั่วผ่านเวลาtoken ลับ: 6 ตัวอักษร hex
comparison:
Rails มีครบ: SecureRandom, secure_compare, has_secure_token, generates_token_for, MessageVerifier ใช้สิ่งเหล่านี้แทน OpenSSL ดิบ
token = SecureRandom.urlsafe_base64(32)               # 256 bits
class ApiKey < ApplicationRecord
  has_secure_token :token, length: 36                 # random, unique
end

ActiveSupport::SecurityUtils.secure_compare(provided, expected)   # constant time
provided == expected                                # leaks timing

# verify a webhook signature
expected = OpenSSL::HMAC.hexdigest("SHA256", webhook_secret, request.raw_post)
head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(expected, request.headers["X-Signature"].to_s)

# sign/encrypt your own data instead of rolling crypto
verifier = Rails.application.message_verifier(:unsubscribe)
link_token = verifier.generate(user.id, expires_in: 7.days, purpose: :unsubscribe)
import crypto from "node:crypto";
const token = crypto.randomBytes(32).toString("base64url");
const a = Buffer.from(provided), b = Buffer.from(expected);
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);   // constant time

16.6 อย่าคิดค้นเอง

  • ใช้ AES-GCM ผ่านไลบรารีที่ดี ห้ามใช้ ECB ห้ามใช้ IV ซ้ำ
  • MD5 และ SHA-1 ใช้เป็น cache key ได้ แต่ไม่ใช่สำหรับความปลอดภัย
  • อย่าคิดรูปแบบ token เอง ถ้าใช้ JWT ให้ปฏิเสธ alg: none