JavaRush /จาวาบล็อก /Random-TH /สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก
Viacheslav
ระดับ

สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก

เผยแพร่ในกลุ่ม
ความปลอดภัยของการแลกเปลี่ยนข้อมูลถือเป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดของแอปพลิเคชันสมัยใหม่ ตั้งแต่สมัยโบราณ ผู้คนมีวิธีการอันชาญฉลาด ซึ่งด้วยการพัฒนาของมนุษยชาติ ได้กลายเป็นศาสตร์แห่งการเข้ารหัสทั้งหมด โดยธรรมชาติแล้ว Java ไม่ได้ยืนหยัดและเสนอ Java Cryptography Architecture (JCA) ให้กับนักพัฒนา การทบทวนนี้ควรให้แนวคิดเบื้องต้นเกี่ยวกับวิธีการทำงาน

คำนำ

ฉันเสนอให้เดินทางย้อนเวลากลับไป ตรงหน้าเราคือโรมโบราณ และเบื้องหน้าเราคือไกอัส จูเลียส ซีซาร์ ผู้ส่งข้อความถึงผู้บังคับบัญชาของเขา มาดูกันว่ามีอะไรอยู่ในข้อความนี้:
สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 2
สิ่งนี้อาจหมายถึงอะไร: "ЕСКЕУГЬГМХИФЯ Е УЛП"? มาเปิด 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

เจ.ซี.เอ.

ตามที่เราได้เรียนรู้ก่อนหน้านี้ Java นำเสนอ Java Cryptography Architecture (JCA) สำหรับการทำงานกับการเข้ารหัส สถาปัตยกรรมนี้มี API (เช่น ชุดอินเทอร์เฟซบางชุด) และผู้ให้บริการ (ที่ใช้งาน):
สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 4
ตามเอกสารระบุว่า " แพลตฟอร์ม Java มีผู้ให้บริการในตัวจำนวนหนึ่ง " นั่นคือแพลตฟอร์ม Java จัดเตรียมชุดผู้ให้บริการในตัวที่สามารถขยายได้หากจำเป็น คุณสามารถดูสิ่งนี้ได้ด้วยตัวเอง:
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

ข้อความสำคัญ

สิ่งแรกที่กล่าวถึงในเอกสาร 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

การเข้ารหัสคีย์แบบสมมาตร

การเข้ารหัสแบบสมมาตรคือการเข้ารหัสที่ใช้คีย์เดียวกันในการเข้ารหัสและถอดรหัส เพื่อที่จะใช้การเข้ารหัสแบบสมมาตร เราจำเป็นต้องมีรหัส เพื่อให้ได้ มันเราใช้KeyGenerator นอกจากนี้เราจะต้องมีคลาสที่แสดงถึงการเข้ารหัส ( Cipher ) ตามที่ระบุไว้ในเอกสาร JCA ในส่วน " การสร้างวัตถุ Cipher " ในการสร้าง Cipher คุณต้องระบุไม่เพียงแค่อัลกอริทึมเท่านั้น แต่ยังรวมถึง "การเปลี่ยนแปลง" ในบรรทัดด้วย คำอธิบายการเปลี่ยนแปลงมีลักษณะดังนี้: "อัลกอริทึม/โหมด/ช่องว่างภายใน":
  • อัลกอริทึม : ที่นี่เราดูชื่อมาตรฐานสำหรับ " อัลกอริทึมการเข้ารหัส (การเข้ารหัส) " ขอแนะนำให้ใช้ AES
  • โหมด : โหมดการเข้ารหัส ตัวอย่างเช่น: ECB หรือ CBC (เราจะพูดถึงเรื่องนี้ในภายหลัง)
  • การเยื้อง/การแยก : แต่ละบล็อคข้อมูลจะถูกเข้ารหัสแยกกัน พารามิเตอร์นี้กำหนดจำนวนข้อมูลที่นับเป็น 1 บล็อก
ตัวอย่างเช่น รับการแปลงต่อไปนี้: "AES/ECB/PKCS5Padding". นั่นคืออัลกอริทึมการเข้ารหัสคือ AES โหมดการเข้ารหัสคือ ECB (ย่อมาจาก Electronic Codebook) ขนาดบล็อกคือ PKCS5Padding PKCS5Padding บอกว่าขนาดของหนึ่งบล็อกคือ 2 ไบต์ (16 บิต) โหมดการเข้ารหัส Codebook อิเล็กทรอนิกส์เกี่ยวข้องกับการเข้ารหัสตามลำดับของแต่ละบล็อก:
สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 7
อาจมีลักษณะเช่นนี้ในโค้ด:
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
เพื่อหลีกเลี่ยงการเล่นซ้ำในกรณีนี้ คุณควรใช้โหมดอื่น - Cipher Block Chaining (CBC) โหมดนี้จะแนะนำแนวคิดของ Initialization Vector (แสดงโดยคลาส IvParameterSpec) และด้วยโหมดนี้ ผลลัพธ์ของการสร้างบล็อกสุดท้ายจะถูกนำมาใช้เพื่อสร้างบล็อกถัดไป:
สถาปัตยกรรมการเข้ารหัส Java: บทนำครั้งแรก - 9
ตอนนี้เรามาเขียนสิ่งนี้ในโค้ด:
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

การเข้ารหัสแบบอสมมาตร

การเข้ารหัสแบบอสมมาตรหรือการเข้ารหัสคีย์สาธารณะเป็นวิธีการเข้ารหัสที่ใช้คู่ของคีย์: คีย์ส่วนตัว (เก็บเป็นความลับจากทุกคน) และคีย์สาธารณะ (เปิดเผยต่อสาธารณะ) การแยกนี้จำเป็นเพื่อแลกเปลี่ยนกุญแจสาธารณะระหว่างทั้งสองฝ่ายอย่างปลอดภัยในการแลกเปลี่ยนข้อมูล ในขณะเดียวกันก็รักษารหัสลับให้ปลอดภัย เมื่อสร้างคู่คีย์ 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

ลายเซ็นดิจิทัล

ดังที่เราเห็นข้างต้น เมื่อทราบคีย์สาธารณะแล้ว คุณสามารถส่งข้อมูลเพื่อให้เฉพาะเจ้าของคีย์ส่วนตัวเท่านั้นที่สามารถถอดรหัสได้ นั่นคือสาระสำคัญของการเข้ารหัสแบบอสมมาตรคือใครก็ตามที่เข้ารหัส แต่มีเพียงเราเท่านั้นที่อ่าน นอกจากนี้ยังมีขั้นตอนย้อนกลับ - ลายเซ็นดิจิทัลซึ่งแสดงโดยคลาส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
ข้างต้นเราได้เห็นว่าทั้งสองฝ่ายแลกเปลี่ยนข้อมูลกันอย่างไร ไม่มีอินเทอร์เฟซมาตรฐานสำหรับการโต้ตอบนี้ใน JCA หรือไม่ ปรากฎว่ามี มาดูกัน.
สถาปัตยกรรมการเข้ารหัส Java: ความคุ้นเคยครั้งแรก - 13

ข้อตกลงที่สำคัญ

สถาปัตยกรรมการเข้ารหัส 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

ใบรับรอง

สำหรับของหวานเรายังมีบางสิ่งที่สำคัญไม่น้อย - ใบรับรอง โดยทั่วไปแล้ว ใบรับรองจะถูกสร้างขึ้นโดยใช้ยูทิลิตี้ 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

คีย์สโตร์ (คีย์สโตร์)

สิ่งสุดท้ายที่ฉันอยากจะพูดถึงคือที่เก็บคีย์และใบรับรอง ซึ่งเรียกว่า 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)

บรรทัดล่าง

ดังนั้นเราจึงได้ตรวจสอบกับคุณถึงการดำเนินการขั้นพื้นฐานและเบื้องต้นภายในกรอบการทำงานของ Java Cryptography Architecture (หรือที่เรียกว่า JCA) เราได้เห็นแล้วว่าการเข้ารหัสแบบสมมาตรและไม่สมมาตรคืออะไร และมีวิธีการใช้งานใน JCA อย่างไร เราได้เห็นวิธีการสร้างใบรับรองและลายเซ็นดิจิทัลและวิธีการใช้งาน ทั้งหมดนี้เป็นเพียงพื้นฐานซึ่งมีสิ่งที่ซับซ้อนและน่าสนใจอีกมากมายอยู่เบื้องหลัง ฉันหวังว่าเนื้อหาการทบทวนนี้จะเป็นประโยชน์และจะทำให้คุณสนใจในการศึกษาเพิ่มเติมในสาขานี้
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION