ความปลอดภัยของการแลกเปลี่ยนข้อมูลถือเป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดของแอปพลิเคชันสมัยใหม่ ตั้งแต่สมัยโบราณ ผู้คนมีวิธีการอันชาญฉลาด ซึ่งด้วยการพัฒนาของมนุษยชาติ ได้กลายเป็นศาสตร์แห่งการเข้ารหัสทั้งหมด โดยธรรมชาติแล้ว Java ไม่ได้ยืนหยัดและเสนอ Java Cryptography Architecture (JCA) ให้กับนักพัฒนา การทบทวนนี้ควรให้แนวคิดเบื้องต้นเกี่ยวกับวิธีการทำงาน
สิ่งนี้อาจหมายถึงอะไร:
ตามเอกสารระบุว่า " แพลตฟอร์ม Java มีผู้ให้บริการในตัวจำนวนหนึ่ง " นั่นคือแพลตฟอร์ม Java จัดเตรียมชุดผู้ให้บริการในตัวที่สามารถขยายได้หากจำเป็น คุณสามารถดูสิ่งนี้ได้ด้วยตัวเอง:
อาจมีลักษณะเช่นนี้ในโค้ด:
เพื่อหลีกเลี่ยงการเล่นซ้ำในกรณีนี้ คุณควรใช้โหมดอื่น - Cipher Block Chaining (CBC) โหมดนี้จะแนะนำแนวคิดของ Initialization Vector (แสดงโดยคลาส IvParameterSpec) และด้วยโหมดนี้ ผลลัพธ์ของการสร้างบล็อกสุดท้ายจะถูกนำมาใช้เพื่อสร้างบล็อกถัดไป:
ตอนนี้เรามาเขียนสิ่งนี้ในโค้ด:
ข้างต้นเราได้เห็นว่าทั้งสองฝ่ายแลกเปลี่ยนข้อมูลกันอย่างไร ไม่มีอินเทอร์เฟซมาตรฐานสำหรับการโต้ตอบนี้ใน JCA หรือไม่ ปรากฎว่ามี มาดูกัน.
คำนำ
ฉันเสนอให้เดินทางย้อนเวลากลับไป ตรงหน้าเราคือโรมโบราณ และเบื้องหน้าเราคือไกอัส จูเลียส ซีซาร์ ผู้ส่งข้อความถึงผู้บังคับบัญชาของเขา มาดูกันว่ามีอะไรอยู่ในข้อความนี้:"ЕСКЕУГЬГМХИФЯ Е УЛП"
? มาเปิด Java Online Compiler กัน เช่นrepl.it
class Main {
public static void main(String[] args) {
String code = "ЕСКЕУГЬГМХИФЯ Е УЛП";
for (char symbol : code.toCharArray()) {
if (symbol != ' ') {
symbol = (char) (symbol - 3);
}
System.out.print(symbol);
}
}
}
ก่อนหน้าเราคือการใช้งาน Caesar Cipher ที่ง่ายที่สุด ตามผลงานของ Suetonius นักประวัติศาสตร์ชาวโรมันโบราณที่มีชื่อว่า "The Lives of the Twelve Caesars" นี่เป็นวิธีที่ Caesar เข้ารหัสข้อความถึงนายพลของเขาอย่างแน่นอน และนี่คือหนึ่งในการอ้างอิงที่เก่าแก่ที่สุดเกี่ยวกับการใช้สิ่งนี้การเข้ารหัส . คำว่า "การเข้ารหัส" มาจากคำภาษากรีกโบราณ "ซ่อน" และ "เขียน" เช่น มันเป็นศาสตร์แห่งเทคนิคความเป็นส่วนตัว Java มีการรองรับการเข้ารหัสเป็นของตัวเอง และเรียกว่าJava Cryptography Architecture (JCA) คำอธิบายสามารถพบได้ในเอกสารอย่างเป็นทางการจาก Oracle - " Java Cryptography Architecture (JCA) " ฉันขอแนะนำให้คุณดูว่าเราได้รับโอกาสอะไรบ้างจาก JCA
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 3](https://cdn.javarush.com/images/article/f16acbfe-a661-4817-bc9a-1663d4501d74/original.jpeg)
เจ.ซี.เอ.
ตามที่เราได้เรียนรู้ก่อนหน้านี้ Java นำเสนอ Java Cryptography Architecture (JCA) สำหรับการทำงานกับการเข้ารหัส สถาปัตยกรรมนี้มี API (เช่น ชุดอินเทอร์เฟซบางชุด) และผู้ให้บริการ (ที่ใช้งาน):![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 4](https://cdn.javarush.com/images/article/d197e3a6-3ee3-4d27-928e-58ebf6d2c408/1024.jpeg)
import java.security.Provider;
import java.security.Security;
class Main {
public static void main(String[] args) {
Provider[] providers = Security.getProviders();
for (Provider p : providers) {
System.out.println(p.getName());
}
}
}
การลงทะเบียนผู้ให้บริการบุคคลที่สามนั้นง่ายมาก ตัวอย่างเช่น: ตัวอย่างนี้เชื่อมโยงหนึ่งในผู้ให้ บริการSecurity.addProvider(new BouncyCastleProvider());
ที่มีชื่อเสียงที่สุด - BouncyCastle แต่ในการรีวิวนี้ เราจะใช้เครื่องมือพื้นฐานเท่านั้น โดยไม่มีไลบรารีของบุคคลที่สาม เอกสารหลักของเรา: " Java Cryptography Architecture (JCA) " การทำความเข้าใจวิธีการทำงานของ JCA จะช่วยให้คุณเข้าใจเทคโนโลยีที่ใช้งาน JCA เดียวกันนี้ได้ง่ายขึ้น ตัวอย่างเช่น: HTTPS (ดู " จาก HTTP สู่ HTTPS ")
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 5](https://cdn.javarush.com/images/article/0cf266c7-9cb0-45ba-9d05-2c25a7be716b/1024.jpeg)
ข้อความสำคัญ
สิ่งแรกที่กล่าวถึงในเอกสาร JCA คือ MessageDigest โดยทั่วไป Digest ในภาษารัสเซียจะเหมือนกัน - การสรุปจะสอดคล้องกับความหมายของ "บทสรุป" แต่ในวิทยาการเข้ารหัสลับ การสรุปคือผลรวมแฮช คุณสามารถจำได้ง่ายว่าใน English Digest สามารถแปลเป็น digest ได้เช่นกัน รายละเอียดเพิ่มเติมสามารถพบได้ในเอกสารประกอบ JCA ในส่วน " MessageDigest " ตามที่เอกสารระบุไว้ MessageDigest จะสร้างผลลัพธ์ที่มีขนาดคงที่เรียกว่าไดเจสต์หรือแฮช การแฮชเป็นฟังก์ชันทางเดียว เช่น ถ้าเราแฮชบางสิ่งบางอย่าง ดังนั้นจากผลลัพธ์ (เช่น จากแฮช) เราจะไม่สามารถรับแหล่งที่มาดั้งเดิมได้ แต่หากมีการแฮชออบเจ็กต์ที่เหมือนกัน (เช่น สตริงที่มีอักขระเหมือนกัน) แฮชของวัตถุนั้นจะต้องตรงกัน ตามที่ระบุไว้ในเอกสารประกอบ บางครั้งแฮชดังกล่าวเรียกอีกอย่างว่า "เช็คซัม" หรือ "ลายนิ้วมือดิจิทัล" ของข้อมูล การแฮชสามารถทำได้โดยใช้อัลกอริธึมที่แตกต่างกัน สามารถดูอัลกอริทึมที่มีอยู่ได้ในเอกสาร " Java Cryptography Architecture Standard Algorithm Name Documentation for JDK 8 " มาทำการแฮชและพิมพ์แฮชไปที่คอนโซล:import javax.xml.bind.DatatypeConverter;
import java.security.*;
public class Main {
public static void main(String[] args) {
try {
MessageDigest digester = MessageDigest.getInstance("SHA-512");
byte[] input = "Secret string".getBytes();
byte[] digest = digester.digest(input);
System.out.println(DatatypeConverter.printHexBinary(digest));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
}
การแฮชอาจมีประโยชน์ เช่น เมื่อจัดเก็บรหัสผ่าน เนื่องจากสามารถตรวจสอบแฮชของรหัสผ่านที่ป้อนกับแฮชที่บันทึกไว้ก่อนหน้านี้ได้ หากแฮชตรงกัน รหัสผ่านก็จะตรงกันด้วย เพื่อการแฮชที่ปลอดภัยยิ่งขึ้น มีการใช้แนวคิดที่เรียกว่า "เกลือ" Salt สามารถนำไปใช้ได้โดยใช้ คลาส SecureRandom ก่อนที่จะดำเนินการเมธอด digest เรามาอธิบายการเพิ่ม "salt" ก่อน:
byte[] salt = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(salt);
digester.update(salt);
แต่แฮชเป็นฟังก์ชันทางเดียว แต่ถ้าคุณต้องการเข้ารหัสและถอดรหัสล่ะ
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 6](https://cdn.javarush.com/images/article/7fc1178f-c80c-4f5b-9a21-812f45504d18/1024.jpeg)
การเข้ารหัสคีย์แบบสมมาตร
การเข้ารหัสแบบสมมาตรคือการเข้ารหัสที่ใช้คีย์เดียวกันในการเข้ารหัสและถอดรหัส เพื่อที่จะใช้การเข้ารหัสแบบสมมาตร เราจำเป็นต้องมีรหัส เพื่อให้ได้ มันเราใช้KeyGenerator นอกจากนี้เราจะต้องมีคลาสที่แสดงถึงการเข้ารหัส ( Cipher ) ตามที่ระบุไว้ในเอกสาร JCA ในส่วน " การสร้างวัตถุ Cipher " ในการสร้าง Cipher คุณต้องระบุไม่เพียงแค่อัลกอริทึมเท่านั้น แต่ยังรวมถึง "การเปลี่ยนแปลง" ในบรรทัดด้วย คำอธิบายการเปลี่ยนแปลงมีลักษณะดังนี้: "อัลกอริทึม/โหมด/ช่องว่างภายใน":- อัลกอริทึม : ที่นี่เราดูชื่อมาตรฐานสำหรับ " อัลกอริทึมการเข้ารหัส (การเข้ารหัส) " ขอแนะนำให้ใช้ AES
- โหมด : โหมดการเข้ารหัส ตัวอย่างเช่น: ECB หรือ CBC (เราจะพูดถึงเรื่องนี้ในภายหลัง)
- การเยื้อง/การแยก : แต่ละบล็อคข้อมูลจะถูกเข้ารหัสแยกกัน พารามิเตอร์นี้กำหนดจำนวนข้อมูลที่นับเป็น 1 บล็อก
"AES/ECB/PKCS5Padding"
. นั่นคืออัลกอริทึมการเข้ารหัสคือ AES โหมดการเข้ารหัสคือ ECB (ย่อมาจาก Electronic Codebook) ขนาดบล็อกคือ PKCS5Padding PKCS5Padding บอกว่าขนาดของหนึ่งบล็อกคือ 2 ไบต์ (16 บิต) โหมดการเข้ารหัส Codebook อิเล็กทรอนิกส์เกี่ยวข้องกับการเข้ารหัสตามลำดับของแต่ละบล็อก:
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 7](https://cdn.javarush.com/images/article/05234cac-36e5-4a9a-a34e-a751bf9a70e3/1024.jpeg)
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.Key;
public class Main {
public static void main(String[] args) throws Exception {
String text = "secret!!secret!!secret!!secret!!";
// Generate new key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
Key key = keygen.generateKey();
// Encrypt with key
String transformation = "AES/ECB/PKCS5Padding";
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(text.getBytes());
System.out.println(DatatypeConverter.printHexBinary(encrypted));
// Decrypt with key
cipher.init(Cipher.DECRYPT_MODE, key);
String result = new String(cipher.doFinal(encrypted));
System.out.println(result);
}
}
ถ้าเราดำเนินการเราจะคาดหวังว่าจะได้เห็นการทำซ้ำเพราะว่า เราระบุ 32 ตัวอักษร อักขระเหล่านี้ประกอบด้วย 2 บล็อก 16 บิต:
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 8](https://cdn.javarush.com/images/article/f6c33a57-014a-41a4-9ca0-47f5d2ee76b4/1024.jpeg)
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 9](https://cdn.javarush.com/images/article/19a3fe6c-1a1a-4de7-916a-9859506f2655/1024.jpeg)
import javax.xml.bind.DatatypeConverter;
import javax.crypto.*;
import java.security.*;
import javax.crypto.spec.IvParameterSpec;
public class Main {
public static void main(String[] args) throws Exception {
// Initialization Vector
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] rnd = new byte[16];
random.nextBytes(rnd);
IvParameterSpec ivSpec = new IvParameterSpec(rnd);
// Prepare key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
Key key = keygen.generateKey();
// CBC
String text = "secret!!secret!!secret!!secret!!";
String transformation = "AES/CBC/PKCS5Padding";
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] enc = cipher.doFinal(text.getBytes());
System.out.println(DatatypeConverter.printHexBinary(enc));
// Decrypt
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
String result = new String(cipher.doFinal(enc));
System.out.println(result);
}
}
ดังที่เราเห็น ผลก็คือเราไม่เห็นบล็อกการเข้ารหัสซ้ำๆ ด้วยเหตุนี้จึงไม่แนะนำให้ใช้โหมด ECB เนื่องจาก ทำให้สามารถเห็นการซ้ำซ้อนและใช้ความรู้นี้ในการถอดรหัส สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ ECB และ CBC ฉันแนะนำให้คุณอ่านเนื้อหา: “ โหมดสมุดรหัสอิเล็กทรอนิกส์ ” แต่การเข้ารหัสแบบสมมาตรมีปัญหาที่ชัดเจน - คุณต้องถ่ายโอนคีย์จากผู้ที่เข้ารหัสไปยังผู้ที่เข้ารหัส และตามเส้นทางนี้ คีย์นี้สามารถดักจับได้ จากนั้นจึงจะสามารถดักจับข้อมูลได้ และการเข้ารหัสแบบอสมมาตรได้รับการออกแบบมาเพื่อแก้ไขปัญหานี้
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 10](https://cdn.javarush.com/images/article/8e7d8a58-c813-4434-bf78-b3c7c58c7292/original.jpeg)
การเข้ารหัสแบบอสมมาตร
การเข้ารหัสแบบอสมมาตรหรือการเข้ารหัสคีย์สาธารณะเป็นวิธีการเข้ารหัสที่ใช้คู่ของคีย์: คีย์ส่วนตัว (เก็บเป็นความลับจากทุกคน) และคีย์สาธารณะ (เปิดเผยต่อสาธารณะ) การแยกนี้จำเป็นเพื่อแลกเปลี่ยนกุญแจสาธารณะระหว่างทั้งสองฝ่ายอย่างปลอดภัยในการแลกเปลี่ยนข้อมูล ในขณะเดียวกันก็รักษารหัสลับให้ปลอดภัย เมื่อสร้างคู่คีย์ KeyGenerator จะไม่เพียงพอสำหรับเราอีกต่อไป เราต้องการKeyPairGenerator ลองดูตัวอย่าง:import javax.crypto.*;
import java.security.*;
public class Main {
public static void main(String[] args) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
KeyPair keyPair = generator.generateKeyPair();
// Encrypt with PRIVATE KEY
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] data = cipher.doFinal("Hello!".getBytes());
// Decrypt with PUBLIC KEY
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
byte[] result = cipher.doFinal(data);
System.out.println(new String(result));
}
}
สิ่งสำคัญคือต้องเข้าใจในที่นี้ว่าเมื่อใช้การเข้ารหัสแบบอสมมาตร เราจะใช้ KeyPair เสมอเพื่อใช้คีย์หนึ่งสำหรับการเข้ารหัสและอีกอันสำหรับการถอดรหัส แต่เพราะว่า ประเด็นของการเข้ารหัสก็คือ มีเพียงผู้รับเท่านั้นที่สามารถถอดรหัสได้ โดยจะถูกเข้ารหัสด้วยกุญแจสาธารณะ และถอดรหัสด้วยกุญแจส่วนตัวเท่านั้น
![สถาปัตยกรรมการเข้ารหัส Java: ความคุ้นเคยครั้งแรก - 11](https://cdn.javarush.com/images/article/47f718fc-9a00-436f-a2fc-bd181e25f42c/original.jpeg)
ลายเซ็นดิจิทัล
ดังที่เราเห็นข้างต้น เมื่อทราบคีย์สาธารณะแล้ว คุณสามารถส่งข้อมูลเพื่อให้เฉพาะเจ้าของคีย์ส่วนตัวเท่านั้นที่สามารถถอดรหัสได้ นั่นคือสาระสำคัญของการเข้ารหัสแบบอสมมาตรคือใครก็ตามที่เข้ารหัส แต่มีเพียงเราเท่านั้นที่อ่าน นอกจากนี้ยังมีขั้นตอนย้อนกลับ - ลายเซ็นดิจิทัลซึ่งแสดงโดยคลาสSignature ลายเซ็นดิจิทัลสามารถใช้อัลกอริทึมต่อไปนี้: " อัลกอริทึมลายเซ็น " เอกสารของ JCA แนะนำให้พิจารณาทั้งสองสิ่งนี้ให้ละเอียดยิ่งขึ้น: DSAwithMD5 และ RSAwithMD5 อะไรจะดีไปกว่า DSA หรือ RSA และอะไรคือความแตกต่างที่คุณสามารถอ่านได้ที่นี่: " สิ่งใดทำงานได้ดีที่สุดสำหรับการถ่ายโอนไฟล์ที่เข้ารหัส - RSA หรือ DSA " หรืออ่านการสนทนาที่นี่: " RSA กับ DSA สำหรับคีย์การตรวจสอบสิทธิ์ SSH " ดังนั้นลายเซ็นดิจิทัล เราจะต้องมี KeyPair และคลาส Signature ใหม่เหมือนเมื่อก่อน หากคุณได้ทดสอบในคอมไพเลอร์ออนไลน์แล้ว ตัวอย่างต่อไปนี้อาจค่อนข้างยากสำหรับพวกเขา ตัวอย่างของฉันวิ่งที่นี่เท่านั้น: rextester.com เรานำเข้าคลาสที่เราต้องการ:import javax.crypto.*;
import java.security.*;
เราจะเขียนวิธีการหลักใหม่ด้วย:
public static void main(String[] args) throws Exception {
// Generate keys
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = SecureRandom.getInstanceStrong();
generator.initialize(2048, random);
KeyPair keyPair = generator.generateKeyPair();
// Digital Signature
Signature dsa = Signature.getInstance("SHA256withRSA");
dsa.initSign(keyPair.getPrivate());
// Update and sign the data
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] data = cipher.doFinal("Hello!".getBytes());
dsa.update(data);
byte[] signature = dsa.sign();
// Verify signature
dsa.initVerify(keyPair.getPublic());
dsa.update(data);
boolean verifies = dsa.verify(signature);
System.out.println("Signature is ok: " + verifies);
// Decrypt if signature is correct
if (verifies) {
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
byte[] result = cipher.doFinal(data);
System.out.println(new String(result));
}
}
นี่คือวิธีการทำงานของลายเซ็นดิจิทัล ลายเซ็นดิจิทัลเป็นหัวข้อที่น่าสนใจ ฉันแนะนำให้คุณดูรายงานในหัวข้อนี้:
![สถาปัตยกรรมการเข้ารหัส Java: ความคุ้นเคยครั้งแรก - 12](https://cdn.javarush.com/images/article/3cab9610-ad30-40ab-bf11-541c3a22090a/1024.jpeg)
![สถาปัตยกรรมการเข้ารหัส Java: ความคุ้นเคยครั้งแรก - 13](https://cdn.javarush.com/images/article/7c98eab3-a01e-4fb1-8687-d962b899446c/original.jpeg)
ข้อตกลงที่สำคัญ
สถาปัตยกรรมการเข้ารหัส Java แนะนำเครื่องมือที่สำคัญ - ข้อตกลงหลักคือโปรโตคอล มันถูกแสดงโดย คลาส KeyAgreement ตามที่ระบุไว้ในเอกสาร JCA โปรโตคอลนี้อนุญาตให้หลายฝ่ายตั้งค่าคีย์การเข้ารหัสเดียวกันโดยไม่ต้องแชร์ข้อมูลลับระหว่างฝ่าย ฟังดูแปลกเหรอ? ถ้าอย่างนั้นเรามาดูตัวอย่างกัน:// 1. Одна из сторон (Алиса) генерирует пару ключей. Encoded публичный ключ отдаёт.
KeyPairGenerator generator = KeyPairGenerator.getInstance("DH");
KeyPair aliceKeyPair = generator.generateKeyPair();
byte[] alicePubKeyEncoded = aliceKeyPair.getPublic().getEncoded();
// 2. Другая сторона (например, Боб) получает открытый ключ Алисы
KeyFactory bobKeyFactory = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(alicePubKeyEncoded);
PublicKey alicePubKey = bobKeyFactory.generatePublic(x509KeySpec);
// Параметры, которые использовала Алиса при генерации ключей
DHParameterSpec dhParamFromAlicePubKey = ((DHPublicKey)alicePubKey).getParams();
// Создаёт свою пару ключей. Отдаёт свой Encoded открытый ключ
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamFromAlicePubKey);
KeyPair bobKeyPair = bobKpairGen.generateKeyPair();
byte[] bobPubKeyEncoded = bobKeyPair.getPublic().getEncoded();
Теперь, у Алисы есть открытый ключ Боба, а у Боба есть открытый ключ Алисы. What дальше?
Как сказано в documentации JCA, у нас есть инструмент KeyAgreement, https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyAgreement который позволяет установить одинаковые ключи шифрования без необходимости обмениваться секретной информацией (т.е. без обмена private key). Соглашение выглядит следующим образом:
// 3. Соглашение по протоколу Диффи-Хеллмана (Diffie–Hellman, DH)
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKeyPair.getPrivate());
// Алиса на основе ключа боба и своего private key создаёт общий shared ключ
KeyFactory aliceKeyFactory = KeyFactory.getInstance("DH");
x509KeySpec = new X509EncodedKeySpec(bobPubKeyEncoded);
PublicKey bobPubKey = aliceKeyFactory.generatePublic(x509KeySpec);
aliceKeyAgree.doPhase(bobPubKey, true);
byte[] aliceSharedSecret = aliceKeyAgree.generateSecret();
SecretKeySpec aliceAesKey = new SecretKeySpec(aliceSharedSecret, 0, 16, "AES");
// Боб на основе ключа Алисы и своего private key создаёт общий shared ключ
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKeyPair.getPrivate());
bobKeyAgree.doPhase(alicePubKey, true);
byte[] bobSharedSecret = bobKeyAgree.generateSecret();
SecretKeySpec bobAesKey = new SecretKeySpec(bobSharedSecret, 0, 16, "AES");
// Общий ключ у Алисы и Боба одинаков
System.out.println("Shared keys are equals: " + Arrays.equals(aliceSharedSecret, bobSharedSecret));
Далее Боб и Алиса, используя общий ключ, про который больше никто не знает, обмениваются зашифрованными данными:
// 4. Боб шифрует сообщение для Алисы
Cipher bobCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
bobCipher.init(Cipher.ENCRYPT_MODE, bobAesKey);
byte[] ciphertext = bobCipher.doFinal("Hello, Alice!".getBytes());
// Передаёт Алисе параметры, с которыми выполнялась шифровка
byte[] encodedParamsFromBob = bobCipher.getParameters().getEncoded();
// 5. Алиса принимает сообщение и расшифровывает его
AlgorithmParameters aesParams = AlgorithmParameters.getInstance("AES");
aesParams.init(encodedParamsFromBob);
Cipher aliceCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
aliceCipher.init(Cipher.DECRYPT_MODE, aliceAesKey, aesParams);
byte[] recovered = aliceCipher.doFinal(ciphertext);
System.out.println(new String(recovered));
ตัวอย่างนี้นำมาจากตัวอย่างเอกสาร JCA: " Diffie-Hellman Key Exchange between 2 Parties " นี่คือลักษณะการเข้ารหัสแบบอสมมาตรโดยประมาณในสถาปัตยกรรมการเข้ารหัส Java โดยใช้โปรโตคอลข้อตกลงคีย์ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเข้ารหัสแบบอสมมาตร วิดีโอแนะนำ:
![สถาปัตยกรรมการเข้ารหัส Java: ความคุ้นเคยครั้งแรก - 14](https://cdn.javarush.com/images/article/1318d366-a118-466a-865f-1cf33ecb033d/original.jpeg)
ใบรับรอง
สำหรับของหวานเรายังมีบางสิ่งที่สำคัญไม่น้อย - ใบรับรอง โดยทั่วไปแล้ว ใบรับรองจะถูกสร้างขึ้นโดยใช้ยูทิลิตี้ keytool ที่มาพร้อมกับ jdk คุณสามารถอ่านรายละเอียดเพิ่มเติมได้ที่นี่: " การสร้างใบรับรอง SSL ที่ลงนามเองโดยใช้คำสั่ง Java keytool " คุณยังสามารถอ่านคู่มือจาก Oracle ได้ ตัวอย่างเช่นที่นี่: " การใช้ keytool เพื่อสร้างใบรับรองเซิร์ฟเวอร์ " ตัวอย่างเช่น ลองใช้Tutorialspoint Java Online Compiler :import sun.security.tools.keytool.CertAndKeyGen;
import sun.security.x509.*;
import java.security.cert.*;
import java.security.*;
// Compiler args: -XDignore.symbol.file
public class Main {
public static void main(String[] args) throws Exception {
CertAndKeyGen certGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
// generate it with 2048 bits
certGen.generate(2048);
PrivateKey privateKey = certGen.getPrivateKey();
X509Key publicKey = certGen.getPublicKey();
// prepare the validity of the certificate
long validSecs = (long) 365 * 24 * 60 * 60; // valid for one year
// enter your details according to your application
X500Name principal = new X500Name("CN=My Application,O=My Organisation,L=My City,C=DE");
// add the certificate information, currently only valid for one year.
X509Certificate cert = certGen.getSelfCertificate(principal, validSecs);
// Public Key from Cert equals Public Key from generator
PublicKey publicKeyFromCert = cert.getPublicKey();
System.out.println(publicKeyFromCert.equals(publicKey));
}
}
ดังที่เราเห็น ใบรับรองให้ความสามารถในการจัดเตรียมคีย์สาธารณะ วิธีนี้มีข้อเสีย - เราใช้sun.security
ซึ่งถือว่ามีความเสี่ยงเพราะ... แพ็คเกจนี้ไม่ได้เป็นส่วนหนึ่งของ Java API สาธารณะ นั่นคือเหตุผลที่ในระหว่างการคอมไพล์จำเป็นต้องระบุพารามิเตอร์ - XDignore.symbol.file
. มีวิธีอื่นคือ - สร้างใบรับรองด้วยตนเอง ข้อเสียคือใช้ API ภายในที่ไม่ได้จัดทำเป็นเอกสาร อย่างไรก็ตาม การรู้เรื่องนี้ก็เป็นประโยชน์ อย่างน้อยที่สุด เนื่องจากจะมองเห็นได้ชัดเจนว่าข้อกำหนด RFC-2459 ถูกใช้อย่างไร: “ โครงสร้างพื้นฐานคีย์สาธารณะของอินเทอร์เน็ต X.509 ” นี่คือตัวอย่าง:
// 1. Генерируем пару ключей
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 2. Определяем данные сертификата
// Определяем срок действия сертификата
Date from = new Date();
Date to = new Date(from.getTime() + 365 * 1000L * 24L * 60L * 60L);
CertificateValidity interval = new CertificateValidity(from, to);
// Определяем subject name, т.е. Name того, с чем ассоциирован публичный ключ
// CN = Common Name. Через точку с запятой могут быть указаны также другие атрибуты
// См. https://docs.oracle.com/cd/E24191_01/common/tutorials/authz_cert_attributes.html
X500Name owner = new X500Name("cn=Unknown");
// Уникальный в пределах CA, т.е. Certificate Authority (тот, кто выдаёт сертификат) номер
BigInteger number = new BigInteger(64, new SecureRandom());
CertificateSerialNumber serialNumber = new CertificateSerialNumber(number);
// Определяем алгоритм подписи сертификата
AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
CertificateAlgorithmId certificateAlgorithmId = new CertificateAlgorithmId(algorithmId);
// 3. По подготовленной информации создаём сертификат
X509CertInfo info = new X509CertInfo();
info.set(X509CertInfo.VALIDITY, interval);
info.set(X509CertInfo.SERIAL_NUMBER, serialNumber);
info.set(X509CertInfo.SUBJECT, owner);
info.set(X509CertInfo.ISSUER, owner);
info.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic()));
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.ALGORITHM_ID, certificateAlgorithmId);
// 4. Подписываем сертификат
X509CertImpl certificate = new X509CertImpl(info);
certificate.sign(keyPair.getPrivate(), "SHA256withRSA");
// 5. Проверка сертификата
try {
// В случае ошибки здесь будет брошено исключение. Например: java.security.SignatureException
certificate.verify(keyPair.getPublic());
} catch (Exception e) {
throw new IllegalStateException(e);
}
![สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 15](https://cdn.javarush.com/images/article/b327b810-a68c-4ec3-b90b-8ab3211ae217/1024.jpeg)
คีย์สโตร์ (คีย์สโตร์)
สิ่งสุดท้ายที่ฉันอยากจะพูดถึงคือที่เก็บคีย์และใบรับรอง ซึ่งเรียกว่า KeyStore เห็นได้ชัดว่าการสร้างใบรับรองและคีย์อย่างต่อเนื่องมีราคาแพงและไม่มีจุดหมาย ดังนั้นจึงจำเป็นต้องเก็บไว้อย่างปลอดภัย มีเครื่องมือสำหรับสิ่งนี้ - KeyStore ที่เก็บคีย์อธิบายไว้ในเอกสารประกอบ JCA ในบท " KeyManagement " API สำหรับการทำงานกับมันชัดเจนมาก นี่เป็นตัวอย่างเล็กๆ น้อยๆ:KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
String alias = "EntityAlias";
java.security.cert.Certificate[] chain = {certificate};
keyStore.setKeyEntry(alias, keyPair.getPrivate(), "keyPassword".toCharArray(), chain);
// Загрузка содержимого (Private Key + Certificate)
Key key = keyStore.getKey(alias, "keyPassword".toCharArray());
Certificate[] certificateChain = keyStore.getCertificateChain(alias);
// Сохранение KeyStore на диск
File file = File.createTempFile("security_", ".ks");
System.out.println(file.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(file)) {
keyStore.store(fos, "keyStorePassword".toCharArray());
}
ดังที่คุณเห็นจากตัวอย่าง มันถูกดำเนินการก่อนload
สำหรับ KeyStore แต่ในกรณีของเรา เราได้ระบุแอตทริบิวต์แรกเป็นโมฆะ เช่น ไม่มีแหล่งที่มาสำหรับ KeyStore ซึ่งหมายความว่า KeyStore ถูกสร้างขึ้นว่างเปล่าเพื่อบันทึกเพิ่มเติม พารามิเตอร์ตัวที่สองก็เป็นโมฆะเช่นกันเพราะ เรากำลังสร้าง KeyStore ใหม่ หากเราโหลด KeyStore จากไฟล์ เราจะต้องระบุรหัสผ่านที่นี่ (คล้ายกับวิธี KeyStore ที่เรียกว่า store)
GO TO FULL VERSION