hash - มีการใช้งาน SHA-256 ใน JavaScript แบบใดบ้างที่โดยทั่วไปถือว่าน่าเชื่อถือ?
ฉันกำลังเขียนระบบล็อกอินสำหรับฟอรั่ม และต้องการเข้ารหัสรหัสผ่านฝั่งไคลเอนต์ด้วย JavaScript ก่อนส่งไปยังเซิร์ฟเวอร์ ฉันกำลังมีปัญหาในการหาว่าการใช้งาน SHA-256 แบบใดที่ฉันสามารถเชื่อถือได้ ฉันคาดหวังว่าจะมีสคริปต์ที่เป็นมาตรฐานที่ทุกคนใช้ แต่พบว่ามีหลายโครงการที่ต่างก็มีการใช้งานของตัวเอง
ผมตระหนักว่าการใช้คริปโตของคนอื่นมักเป็นการกระโดดด้วยความเชื่อถ้าไม่ได้มีคุณสมบัติพอที่จะตรวจสอบด้วยตัวเอง และก็ไม่มีคำจำกัดความสากลของคำว่า 'น่าเชื่อถือ' แต่เรื่องนี้ดูเหมือนจะเป็นสิ่งที่พบได้ทั่วไปและสำคัญพอที่ควรมีฉันทามติบางอย่างเกี่ยวกับสิ่งที่ควรใช้ ผมแค่บริสุทธิ์ใจเกินไปหรือเปล่า?
แก้ไขเพราะมันมักจะปรากฏบ่อยในความคิดเห็น: ใช่ เราทำการแฮชที่เข้มงวดมากขึ้นอีกครั้งทางฝั่งเซิร์ฟเวอร์ การแฮชทางฝั่งไคลเอนต์ไม่ใช่ผลลัพธ์สุดท้ายที่เราบันทึกในฐานข้อมูล การแฮชทางฝั่งไคลเอนต์เป็นเพราะไคลเอนต์มนุษย์ร้องขอ พวกเขาไม่ได้ให้เหตุผลเฉพาะเจาะจงว่าทำไม อาจจะพวกเขาแค่ชอบทำเกินจำเป็น
คำตอบ #1
บน https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest ฉันพบโค้ดตัวอย่างนี้ที่ใช้โมดูล js ภายใน:
async function sha256(message) {
// encode as UTF-8
const msgBuffer = new TextEncoder().encode(message);
// hash the message
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
// convert ArrayBuffer to Array
const hashArray = Array.from(new Uint8Array(hashBuffer));
// convert bytes to hex string
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
โปรดทราบว่า crypto.subtle สามารถใช้งานได้เฉพาะบน https หรือ localhost เท่านั้น - ตัวอย่างเช่น สำหรับการพัฒนาในเครื่องของคุณด้วย python3 -m http.server คุณจำเป็นต้องเพิ่มบรรทัดนี้ไปยัง /etc/hosts ของคุณ: 0.0.0.0 localhost
รีบูต - แล้วคุณสามารถเปิด localhost:8000 พร้อม crypto.subtle ที่ทำงานได้
คำตอบ #2
เบราว์เซอร์สมัยใหม่ส่วนใหญ่ตอนนี้รองรับการทำงานด้านคริปโตโดยตรง ดูที่ https://developer.mozilla.org/en-US/docs/Web/API/Crypto.
อย่างไรก็ตาม การเข้ารหัสด้านไคลเอนต์โดยปกติไม่ใช่แนวคิดที่ดีเท่าที่อาจดูเหมือน
หลักการพื้นฐานคือคุณ (ที่ฝั่งเซิร์ฟเวอร์) ต้องสมมติว่าทุกสิ่งที่เกิดขึ้นบนฝั่งไคลเอนต์อาจเป็นอันตราย และอาจไม่ได้รันโค้ดของคุณเลย หากการทำงานด้านการเข้ารหัสของคุณมีวัตถุประสงค์เพื่อปกป้องข้อมูลของผู้ใช้งานจากการค้นพบหรือการรบกวนของบุคคลที่สาม ชั้นการเข้ารหัสที่เหมาะสมในการใช้งานคือ TLS แทนที่จะสร้างการเข้ารหัสของคุณเองในฝั่งไคลเอนต์
การส่งข้อมูลที่ละเอียดอ่อนผ่าน TLS ไม่ต้องการการเข้ารหัสเพิ่มเติม ระหว่างการส่งนอกเหนือจากที่ TLS จัดให้โดยตรงเพื่อให้เป็นไปตามกฎหมายหรือแนวปฏิบัติที่ดีที่สุด หากคุณกำลังส่งข้อมูลที่ละเอียดอ่อน ให้ส่งผ่าน TLS จากมุมมองของแอปพลิเคชันของคุณ สิ่งนี้จะ ดูเหมือน ข้อความธรรมดา แต่ก็เป็นเรื่องที่เหมาะสมโดยสมบูรณ์และเป็นเพียงการสะท้อนถึงระดับนามธรรมของการเข้ารหัส ไม่ใช่ระดับความปลอดภัยที่มันจัดให้ เพียงเพราะกลไกความปลอดภัยใช้งานง่ายไม่ได้หมายความว่าการป้องกันที่มันให้มานั้นเรียบง่าย
และอย่าพูดว่า: “ฉันกำลังใช้ TLS อยู่แล้ว แต่ฉันกำลังทำให้มันดียิ่งขึ้นโดยทำฝั่งลูกค้าด้วย” การทำให้แอปพลิเคชันของคุณซับซ้อนขึ้นด้วยส่วนประกอบด้านความปลอดภัยเพิ่มเติมไม่ได้เพิ่มความปลอดภัย แท้จริงแล้ว คุณกำลังแลกความปลอดภัยระยะยาวเพื่อความรู้สึกว่าคุณกำลังทำมากขึ้น
บริบทที่ระบุสำหรับคำถามนี้เป็นสถานที่ที่ไม่เหมาะสมอย่างยิ่งในการทำการเข้ารหัสฝั่งลูกค้า มีเหตุผลที่ดีว่าทำไมบริษัทเทคโนโลยีใหญ่ๆ จึงไม่ทำสิ่งที่คำถามนี้แนะนำ และพวกเขาก็สามารถป้องกันผู้โจมตีระดับรัฐชาติได้สำเร็จ
กรณีที่เหมาะสมกับการเข้ารหัสฝั่งเบราว์เซอร์ของลูกค้า คือเมื่อพิจารณาว่าลูกค้าเป็นระบบแยกตัวและการปกป้องผู้ใช้ไม่คาดว่าจะได้รับการรักษาโดยผู้ดูแลเซิร์ฟเวอร์ นี่เป็นสถานการณ์ที่หายากอย่างยิ่ง แต่ปรากฏในสิ่งต่างๆ เช่น แอปพลิเคชันเว็บแบบออฟไลน์
คำตอบ #3
สำหรับผู้ที่สนใจ นี่คือโค้ดสำหรับสร้างแฮช SHA-256 โดยใช้ sjcl:
import sjcl from 'sjcl'
const myString = 'Hello'
const myBitArray = sjcl.hash.sha256.hash(myString)
const myHash = sjcl.codec.hex.fromBits(myBitArray)
คำตอบ #4
การใช้งาน SHA-256 ของ Forge นั้นรวดเร็วและเชื่อถือได้
ในการทดสอบหลายการใช้งาน SHA-256 ของ JavaScript ให้ไปที่ http://brillout.github.io/test-javascript-hash-implementations/.
ผลลัพธ์บนเครื่องของฉันบ่งชี้ว่า forge เป็นการใช้งานที่เร็วที่สุดและยังเร็วกว่า Stanford Javascript Crypto Library (sjcl) ที่กล่าวถึงในคำตอบที่ได้รับการยอมรับอย่างมาก
Forge มีขนาด 256 KB แต่การแยกโค้ดที่เกี่ยวข้องกับ SHA-256 จะลดขนาดเหลือ 4.5 KB ดูได้ที่ https://github.com/brillout/forge-sha256
คำตอบ #5
ไม่, ไม่มีวิธีใดที่ใช้ JavaScript บนเบราว์เซอร์เพื่อปรับปรุงความปลอดภัยของรหัสผ่านได้ ผมขอแนะนำอย่างยิ่งให้คุณอ่านบทความนี้ ในกรณีของคุณ ปัญหาใหญ่ที่สุดคือปัญหาไข่กับไก่:
ปัญหา 'ไก่กับไข่' คืออะไรในการส่งมอบการเข้ารหัส Javascript?
ถ้าคุณไม่เชื่อถือเครือข่ายว่าจะส่งรหัสผ่านได้ หรือในทางที่แย่กว่านั้นไม่เชื่อถือเซิร์ฟเวอร์ว่าจะไม่เก็บความลับของผู้ใช้ คุณก็ไม่สามารถเชื่อถือมันที่จะส่งรหัสความปลอดภัยได้ ผู้โจมตีเดียวกันที่กำลังดักจับรหัสผ่านหรืออ่านไดอารี่ก่อนที่คุณจะใช้การเข้ารหัส ก็กำลังยึดรหัสการเข้ารหัสหลังจากที่คุณใช้แล้วเช่นกัน
[...]
ทำไมฉันถึงไม่สามารถใช้ TLS/SSL เพื่อส่งโค้ดคริปโตของ Javascript ได้?
คุณทำได้ มันยากกว่าที่ฟัง แต่คุณสามารถส่งผ่าน Javascript crypto ไปยังเบราว์เซอร์ได้อย่างปลอดภัยโดยใช้ SSL ปัญหาคือ เมื่อคุณสร้างช่องทางที่ปลอดภัยด้วย SSL แล้ว คุณก็ไม่จำเป็นต้องใช้การเข้ารหัส Javascript อีกต่อไป; คุณมีการเข้ารหัส "จริง"
ซึ่งนำไปสู่สิ่งนี้:
ปัญหาของการรันโค้ดคริปโตใน Javascript คือแทบทุกฟังก์ชันที่คริปโตพึ่งพาสามารถถูกแทนที่อย่างเงียบ ๆ โดยเนื้อหาใด ๆ ที่ใช้สร้างหน้าโฮสต์ได้ ความปลอดภัยของคริปโตสามารถถูกทำลายตั้งแต่ต้นกระบวนการ (โดยการสร้างตัวเลขสุ่มปลอม หรือโดยการดัดแปลงค่าคงที่และพารามิเตอร์ที่ใช้โดยอัลกอริทึม) หรือภายหลัง (โดยการนำวัสดุคีย์กลับไปยังผู้โจมตี) หรือ --- ในสถานการณ์ที่มีโอกาสเกิดมากที่สุด --- โดยการข้ามขั้นตอนคริปโตทั้งหมด
ไม่มีวิธีที่เชื่อถือได้สำหรับโค้ด Javascript ใด ๆ ในการตรวจสอบสภาพแวดล้อมการทำงานของมัน โค้ดเข้ารหัสของ Javascript ไม่สามารถถามได้ว่า “ฉันกำลังจัดการกับตัวสร้างตัวเลขสุ่มจริง ๆ หรือกับสำเนาของมันที่ผู้โจมตีจัดเตรียมมา?” และมันแน่นอนไม่สามารถยืนยันได้ว่า “ไม่มีใครได้รับอนุญาตให้ทำอะไรกับความลับเข้ารหัสนี้ ยกเว้นในวิธีที่ฉัน ผู้เขียนอนุมัติ” นี่คือคุณสมบัติสองอย่างที่มักพบในสภาพแวดล้อมอื่น ๆ ที่ใช้การเข้ารหัส และเป็นไปไม่ได้ใน Javascript.
โดยพื้นฐานแล้ว ปัญหาคือเรื่องนี้:
- ลูกค้าของคุณไม่ไว้วางใจเซิร์ฟเวอร์ของคุณ ดังนั้นพวกเขาจึงต้องการเพิ่มรหัสความปลอดภัยเพิ่มเติม
- รหัสความปลอดภัยนั้นถูกส่งโดยเซิร์ฟเวอร์ของคุณ (เซิร์ฟเวอร์ที่พวกเขาไม่เชื่อถือ)
หรืออีกทางหนึ่ง,
- ลูกค้าของคุณไม่เชื่อถือ SSL ดังนั้นพวกเขาต้องการให้คุณใช้รหัสความปลอดภัยเพิ่มเติม
- รหัสรักษาความปลอดภัยนั้นถูกส่งผ่าน SSL
หมายเหตุ: นอกจากนี้ SHA-256 ไม่เหมาะสำหรับสิ่งนี้ เนื่องจากสามารถโจมตีแบบ Brute force รหัสผ่านที่ไม่มีการผสมเกลือและไม่ทำซ้ำได้ง่าย หากคุณตัดสินใจที่จะทำเช่นนี้ ควรมองหาการใช้งาน bcrypt, scrypt หรือ PBKDF2
คำตอบ #6
ฉันพบว่าการใช้งานนี้ง่ายมาก นอกจากนี้ยังมีใบอนุญาตแบบ BSD ที่ใจกว้าง:
jsSHA: https://github.com/Caligatio/jsSHA
ฉันต้องการวิธีด่วนในการรับค่าตัวแทนแบบสตริงเลขฐานสิบหกของแฮช SHA-256 มันใช้เพียง 3 บรรทัดเท่านั้น:
var sha256 = new jsSHA('SHA-256', 'TEXT');
sha256.update(some_string_variable_to_hash);
var hash = sha256.getHash("HEX");
