4 Cryptography Concept ที่ Developer ทุกคนควรรู้

Kittitorn Kanokwalai
SCB TechX
Published in
5 min readNov 26, 2021

ขึ้นชื่อว่าเป็น Software Developer เชื่อว่าแทบจะทุกคนสามารถส่งมอบงานตาม Requirement ที่ได้รับมาได้อยู่แล้ว ใช่มั้ยครับ!? แต่ทุกวันนี้เวลาที่เราทำ Application หรือ API ขึ้นมาให้คนจำนวนมากใช้ แค่นั้นอาจจะยังไม่พอ

ยกตัวอย่างให้เห็นภาพถ้าเราได้ Requirement มาว่า

“ให้ส่ง Message จากระบบ A ไปหาระบบ B”

สิ่งนี้คือ Functional Requirement ครับ แต่มันยังมีส่วนที่เป็น Non-functional Requirement ซ่อนอยู่

“ระหว่างทาง Message ที่ระบบ B ได้รับนั้นต้องไม่โดนแก้ไข”

“Message มาจากระบบ A จริง”

“อนุญาตให้ระบบ B อ่านได้เท่านั้น”

หนึ่งในนั่นคือเรื่อง Security นั่นเอง (มีอีกหลายอันรออยู่) วันนี้เลยจะมาคุยเรื่อง Cryptography ซึ่งเป็นเรื่องย่อยนึงของจักรวาล Security ครับ

Cryptography คือ ศาสตร์การเข้ารหัสข้อความที่เป็นความลับ ให้คนที่ต้องการอ่านได้เท่านั้น แบ่งเป็น 2 ส่วนหลัก คือ Encryption (การเข้ารหัส) และ Decryption (การถอดรหัส)

Concept การรักษาความลับมีมานานแล้ว อย่างสมัยโบราณเวลาจะส่งข้อความที่เป็นความลับข้ามเมือง ทำโดยการสักข้อความไปที่หัวของคนส่งสาร รอให้ผมยาวแล้วเดินทางไปให้อีกเมืองโกนหัวเพื่ออ่าน หรือสมัยสงครามโลกก็มีเทคนิคการเข้ารหัสแผนการรบที่ฉลาดๆ กันแบบที่เราเห็นในหนังหลายเรื่อง

จนมาถึงยุคของ Computer และ Internet ทำให้วิธีการแบบเดิมหลายอันถอดรหัสได้ง่าย วิธีไหนใช้รักษาความลับไม่ได้ดี ก็ค่อยๆหายไป และมีคนคิดวิธีการใหม่ขึ้น จนมาถึงทุกวันนี้ ได้มีการใช้ทฤษฎีคณิตศาสตร์มาช่วยนั่นเองครับ

เอาล่ะ! เรามาเข้าเรื่องกันซักที,,

แนะนำ 4 Cryptography Concept ที่ใช้กันอย่างแพร่หลาย และเป็นพื้นฐานของ Technology ส่วนใหญ่ในปัจจุบัน

1. Hash

Hash คือการแปลงข้อมูล ให้เป็นรูปแบบที่มีความยาวเท่าเดิมเสมอ มีคุณสมบัติหลักตามนี้ครับ (สำหรับ Algorithm เบื้องหลังจะมีหลายวิธีไม่ได้ลง Detail นะครับ)

  • Input สั้นหรือยาวแค่ไหน จะได้ Output ยาวเท่าเดิมเสมอ
  • เป็นการแปลงข้อมูลทางเดียว ไม่สามารถแปลงข้อมูลกลับได้ (Output ของ Hash เลยเรียกว่า Digest เพราะโดนย่อยไปแล้วครับ)
  • ถ้า Input เหมือนเดิม Output ที่ได้จะออกมาเหมือนเดิม หรือถ้า Input เปลี่ยนแม้แต่นิดเดียว Output ที่ได้ออกมาจะเปลี่ยนไปเลย

ลองมา Implement ง่ายๆ ด้วย javascript กันซักนิดครับ โดย Module ที่ใช้จะเป็น Build In ของ NodeJs ที่ชื่อว่า crypto

1. สร้างไฟล์ hash.js

const { createHash } = require('crypto');
const input = 'secret';
const hashed = createHash('sha256').update(input).digest('hex');
console.log(hashed);

2. ลอง Execute ดูด้วย node hash.js จะได้ผลลัพธ์แบบนี้ครับ

2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b

อธิบาย:ใน Codeใช้หนึ่งใน hash function ชื่อว่า SHA256 ย่อมาจาก Secure Hash Algorithm โดยตัวเลข 256 ด้านหลังคือความยาวของ Output จะเป็น 256 bit แสดงเป็น hex String ครับ

ถ้าเราลองเล่น Code ด้านบนไปซักพัก เราจะเห็นว่า ถ้าเราใส่ Input เดิมเข้าไป เราจะได้ Output เหมือนเดิมตลอด ซึ่งถ้าเอาไปใช้จริงมันไม่ Secure แน่ๆ ครับ เพราะแค่ทำ Hash เตรียมไว้ก่อน เราก็สามารถเดา Input หรือใช้ซ้ำได้แล้ว เลยมีอีกหนึ่งวิธีมาช่วยเรียกว่า

Salt

Salt คือเทคนิคการเพิ่ม Random Data ต่อไปกับ Input แล้วค่อยทำการ Hash ทำให้ Output ที่ออกมาไม่เหมือนเดิม ทำให้ถึงแม้เทียบได้ยากมากขึ้นไปอีก ต้องเก็บค่าของ Salt เอาไว้กับ Digest เพื่อเทียบกันภายหลังด้วยครับ

ลองแก้ไฟล์ hash.js เพิ่ม Salt เข้าไปแล้วลอง Execute ดูครับ

const { createHash, randomBytes } = require('crypto');
const input = 'secret';
const salt = randomBytes(16).toString('hex')
con hashed = createHash('sha256').update(input + salt).digest('hex');
console.log(hashed);

2. Symmetric Encryption

พูดถึง Symmetric Encryption ผมจะชอบจินตนาการง่ายๆ ว่าเอา Message ใส่ตู้เซฟแบบใส่รหัสเอาไว้ คือ ไม่ทำให้ Message เสียหาย และไม่ว่าคนที่เปิดตู้เพื่อเอาความลับใส่ (Encrypt) คนที่เปิดออกมาอ่าน (Decrypt) ต้องรู้รหัสเดียวกัน หรือเรียกว่าเป็นแบบ ​Shared Key หรือจะจำตามรูปนี้ก็ได้ครับ

Symmetric Encryption

ตัวอย่างการ Implement ด้วย NodeJs เหมือนเดิมครับ

  1. สร้างไฟล์ sym-encrypt.js
const { createCipheriv, createDecipheriv, randomBytes } = require('crypto');
const message = 'I need help';
const key = randomBytes(32);
const iv = randomBytes(16);
// Encrypt
const cipher = createCipheriv('aes256', key, iv);
const encrypted = cipher.update(message, 'utf-8', 'hex') + cipher.final('hex');
console.log('Encrypted:', encrypted);
// Decrypt
const decipher = createDecipheriv('aes256', key, iv);
const decrypted = decipher.update(encrypted, 'hex', 'utf-8') + decipher.final('utf-8');
console.log('Decrypted:', decrypted);

2. Execute ดูด้วย node sym-encrypt.js

Encrypted: c636662560dc907a4e22cdbb850c7db6
Decrypted: I need help

อธิบาย: ตัวอย่างจะเป็นวิธีนึงชื่อว่า AES256 ย่อมาจาก Advanced Encryption Standard ส่วนตัวเลขด้านหลังจะเป็น ขนาดของ Key ที่ใช้ จะเห็นว่าใช้ Random ขึ้นมา 32 bytes จับ x8 ดูจะได้ 256 bit พอดี ทั้ง Encrypt, Decrypt ใช้ Key ตัวเดียวกัน

อีกอันนึงที่น่าสนใจคือ ​IV หรือ Initialization Vector เป็นการทำให้จับ Sequence ของการ Encrypt และเดายากมากขึ้นลองใช้ Keyword ไปหาอ่านเพิ่มดูได้ครับ ส่วนนี้เป็น Optional จะใส่หรือไม่ใส่ก็ได้

3. Asymmetric Encryption

ลองจินตนาการกันดูอีกซักครั้ง Asymmetric Encryption คราวนี้ลักษณะเหมือนกระปุกออมสินครับ ใครจะมาหยอดเหรียญก็ได้ แต่คนที่จะเอาเงินออกมาได้ คือคนที่ถือกุญแจเท่านั้น

Key Pair

การจะทำ Asymmetric Encryption ได้ ต้องมีสิ่งที่เรียกว่า Key Pair ประกอบด้วย Public Key และ Private Key โดยที่มันเกิดมาเพื่อคู่กันเท่านั้น ไม่สามารถสับเปลี่ยนคู่และแยกจากกันได้ครับ แต่ละคู่จะไม่ซ้ำกันเลยครับ (มีหลักการคณิตศาสตร์ที่น่าทึ่งเรียกว่าจำนวนเฉพาะอยู่ฉากหลัง)

วิธีการคือ เราจะแจก Public Key แก่ทุกคน แล้วเก็บ Private Key ไว้ที่ตัวเองคนเดียว ตามชื่อเลยครับ คนที่จะส่งข้อความหาเราทำการ Encrypt มาด้วย Public Key มีแค่เราที่ถือ Private Key เท่านั้นที่จะสามารถ Decrypt มาอ่านได้

Asymmetric Encryption

Implement กันอีกซักรอบ มาเริ่มที่การสร้าง Key Pair ก่อนครับ การสร้างมี Tools ที่ทำได้เยอะมาก ส่วนใหญ่นิยมใช้ openssl กัน Library แต่ละภาษาก็ทำได้ ตัวอย่างจะขอใช้ NodeJs เหมือนเดิมเพื่อให้ไม่ต้อง Install อะไรเยอะครับ

1. สร้างไฟล์ keygen.js

const { generateKeyPairSync } = require('crypto');
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
}
});
console.log(publicKey);
console.log(privateKey);

2. ลอง Execute ดูได้เลยครับ node keygen.js จะได้ผลประมาณนี้ (อันนี้ตัดมา รันจริงจะเห็นยาวกว่านี้ครับ)

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtDzeFYM6m7HgB3+MXIuR
TgoUOoS8027MsAvcwW6s3v5mZ16LiMBac1bMYI9qQOtGIQmlSBVtgLf4JcfVs9dO
...-----END PUBLIC KEY----------BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC0PN4VgzqbseAH
f4xci5FOChQ6hLzTbsywC9zBbqze/mZnXouIwFpzVsxgj2pA60YhCaVIFW2At/gl
...-----END PRIVATE KEY-----

อธิบาย: เราเรียก function ใช้งานด้วย Algorithm RSA ย่อมาจาก Rivest–Shamir–Adleman วิธีนี้ใช้กันอยู่แทบทุกหย่อมหญ้าบนโลกมนุษย์เลยครับ ส่วน Options ที่ใส่ด้านหลังคือ Spec ของ Key ว่าเราจะ format ในรูปแบบไหน

3. ตอนนี้เราแค่ Generate Key แล้ว Log ออกมาเท่านั้น จะทำ Asymmetric Encryption ต้องเก็บ Key Pair เอาไว้ใช้งานต่อไป แก้ไฟล์เดิม keygen.js เป็น Write ลงไฟล์แบบนี้ก็ได้ครับ ลอง Execute จะได้ public.pem กับ private.pem มาสองไฟล์

อันนี้เองในทางปฏิบัติเราจะเอา public.pem แจกให้กับสาธารณชน บางที่ก็ทำเป็น Public API ให้ Get ได้เลย ส่วน private.pem เก็บไว้ไม่แพร่งพรายออกไปไหน

const fs = require('fs')...
// console.log(publicKey);
// console.log(privateKey);
fs.writeFileSync('public.pem', publicKey);
fs.writeFileSync('private.pem', privateKey);

4. สร้างไฟล์ asym-encrypt.js แล้วรันดูได้เลยครับ

const { publicEncrypt, privateDecrypt } = require('crypto');
const fs = require('fs');
// Encrypt
const publicKey = fs.readFileSync('public.pem');
const message = 'I need help';
const encrypted = publicEncrypt(publicKey, Buffer.from(message));
console.log("Encrypted:", encrypted.toString('hex'));
// Decrypt
const privateKey = fs.readFileSync('private.pem');
const decrypted = privateDecrypt(privateKey, encrypted);
console.log("Decrypted:", decrypted.toString('utf-8'));

อธิบาย: Code นี้จะไปอ่าน Key ที่เราใส่ไฟล์ไว้ มา Encrypt กับ Decrypt ให้ดูครับ แต่ตัว function เองรับแบบเป็น Byte Buffer อาจจะต้องมีแปลง Encoding ใส่ลงไปหน่อย

NOTE: ถ้าลองเล่นดูใส่ Message ไปยาวมากๆ จะเกิด Exception: data too large for key size นี่เองเป็นหนึ่งข้อจำกัดของ Encryption แบบนี้ครับ

Symmetric Encryption VS Asymmetric Encryption

  • Asymmetric ทำให้ Key ที่ต้องเก็บในระบบน้อยกว่า Symmetric และทำให้มีความเสี่ยงที่ Key จะหลุดน้อยกว่า เพราะไม่ต้องแชร์ Key ร่วมกัน
  • Asymmetric ทำได้ช้ากว่า Symmetric ถ้าส่ง Message ปริมาณเยอะ ความเร็วจะแตกต่างกันพอสมควร
  • Asymmetric มีข้อจำกัดเรื่องใช้กับ Message ขนาดใหญ่ ตอนใช้งาน จำเป็นต้องใช้ร่วมกันทั้งสองแบบ ไว้มีโอกาสจะมาลงรายละเอียดเพิ่มให้ครับ

4. Digital Signature

เชื่อว่าคนไทยส่วนใหญ่ ต้องคุ้นเคยกับการเซ็นต์ชื่อรับรองความถูกต้องของสำเนา นี่เป็นจุดประสงค์ของ Digital Signature เลยครับ คือการรับรองว่า Message ที่ส่งให้ไปมาจากเราจริง และไม่โดนแก้ โดยใช้ Hash และ Asymmetric Encryption ร่วมกัน Step เป็นแบบนี้ครับ

ฝั่งคนส่ง (Sign Signature)

  1. นำ Message ที่ต้องการจะส่งมาเข้า Hash ได้เป็น Digest
  2. นำ Digest ที่ได้จากข้อ 1. มาทำ Asymmetric Encryption ตรงนี้เองจะแตกต่างตรงที่เราจะใช้ Private Key ในการ Encrypt แทน พูดง่ายๆคือ กลับข้างกันจากปกติ จากอันนี้เราจะเรียกมันว่า Signature ครับ
  3. ส่ง Signature ไปคู่กับ Message เป็นอันเสร็จพิธี
Digital Signature (Sign)

ฝั่งคนรับ (Verify Signature)

  1. นำ Signature มา Decrypt ด้วย Public Key
  2. แยกส่วนที่เป็น Message ที่ได้รับเข้า Hash แบบเดียวกันกับที่ฝั่งคนส่งทำ
  3. นำ Result ของ Hash มาเทียบกัน ถ้าตรงกันแสดงว่า Message ที่ได้รับ มาจากคนที่ถือ Private Key มาจริง และไม่โดนแก้ไขอีกด้วย
Digital Signature (Verify)

ข้อควรระวัง!!!

Signature ไม่มีคุณสมบัติเก็บความลับ แค่ใช้ยืนยันว่าเป็นข้อความจากคนส่งเท่านั้น ระหว่างทาง Message มีโอกาสถูกอ่านได้อยู่เพราะ Public Key ใครก็อาจจะมีได้ จำเป็นต้องใช้ร่วมกับ Encryption ด้วยครับ

ตัวอย่าง Implement ด้วย NodeJs สร้างไฟล์ signature.js ตามนี้มาลอง Execute เล่นดูได้เลยครับ

const { createSign, createVerify } = require('crypto');
const fs = require('fs');
const message = "I need help";// Sign
const privateKey = fs.readFileSync('private.pem');
const signature = createSign('rsa-sha256').update(message).sign(privateKey, 'hex');
console.log("Signature:", signature);
// Verify
const publicKey = fs.readFileSync('public.pem');
const verified = createVerify('rsa-sha256').update(message).verify(publicKey, signature, 'hex');
console.log("Verified:", verified);

อธิบาย: เราไปอ่านไฟล์ Key ที่ สร้างไว้ก่อนหน้ามา Sign และ Verify ได้เลยครับ จะเห็นว่า RSA-SHA256 อันนี้จะเป็น Algorithm ของ Asymmetric Encryption รวมกับ Hash ตามตัวอย่างก่อนหน้า

สรุป

เราอาจจะไม่จำเป็นต้องถึงขั้นเข้าใจคณิตศาสตร์เบื้องหลัง (ใครอยากรู้ไปหาอ่านเพิ่มก็ไม่ว่ากัน) เพราะแต่ละภาษาก็มี Library ให้เรียกใช้อยู่แล้ว เพียงแค่เข้าใจ ประโยชน์และข้อจำกัดของวิธีการแต่ละแบบ เพื่อที่จะเอาไปใช้ออกแบบ ระบบของเรา ได้อย่างเหมาะสม ตอบโจทย์ด้าน Security มากขึ้น

สุดท้ายจะเห็นว่า Cryptography มีความเป็นการวิวัฒนาการ มีการเปลี่ยนแปลงไปตามยุคสมัย เราอาจจะต้องคอยติดตามดูว่า วิธีการไหนใช้ได้อยู่ หรือไม่ได้แล้ว ไม่แน่ว่าการเปลี่ยนแปลงของ Technology ในอนาคตอาจจะถอดรหัสวิธีการเหล่านี้ได้แบบสบายๆ ถึงเวลานั้นเราค่อยมาเริ่มเรียนรู้ทั้งหมดกันใหม่นะครับ :)

--

--

Kittitorn Kanokwalai
SCB TechX

A Software Engineer who in love with Biology, History and Philosophy.