###### tags: `📆2021年` `#💻編程/🛡資訊安全` `#💻編程/🌠Java/99_其他` `🗂Overview` # 密碼學 - 密碼學是網路安全、信息安全、區塊鏈…學科的基礎 - 歷史: - 古典密碼學:替換法、移位法(凱撒加密) 破解方法:頻率分析法(如英文中,e的出現的頻率最高) - 近代密碼學:恩尼格瑪密碼機(核心也是使用移位法、替換法)-->被圖靈破解 - 現代密碼學: 1. 散列函數(哈希函數):常見加密方式,MD5、SHA-1、SHA-256 2. 對稱加密 - 流加密:[流加密 -wiki](https://zh.wikipedia.org/wiki/%E6%B5%81%E5%AF%86%E7%A0%81) - 塊加密:[塊加密 -wiki](https://zh.wikipedia.org/zh-tw/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81) 3. 非對稱加密 ## 🔐相關知識 ### bit和byte的區別 - bit,位元,二進制(0或1),電腦運算基礎,一般以b表示 資料傳輸bps(bit per second):1Mbps = 1024 Kbps / 8 = 128 KB/s ```java public static void main(final String srg[]){ final String str = "Hello"; System.out.println("print bit"); printBit(str.getBytes()); } private static void printBit(final byte[] bytes) { for (final byte b : bytes) System.out.println(Integer.toBinaryString(b)); } ``` - byte,字節、位元組,電腦檔案大小基本單位,一般以B表示 1 B = 8 b ```java public static void main(final String srg[]){ final String str = "Hello"; System.out.println("print byte"); printByte(str.getBytes()); } private static void printByte(final byte[] bytes) { for (final byte b : bytes) System.out.println(b); } ``` - 中文-->byte 1. UTF-8,一個中文 = 3 byte 2. GBK,一個中文 = 2 byte ```java public static void main(final String srg[]) throws UnsupportedEncodingException { final String str = "測"; System.out.println("print bit"); printBit(str.getBytes("GBK")); System.out.println("print byte"); printByte(str.getBytes("GBK")); } private static void printBit(final byte[] bytes) { for (final byte b : bytes) System.out.println(Integer.toBinaryString(b)); } private static void printByte(final byte[] bytes) { for (final byte b : bytes) System.out.println(b); } ``` ### base64 - base64不是加密算法,是可讀性算法(為了可讀性才計算) - base64由64個字符組成,`a-z`、`A-Z`、`0-9`、`+`和`/` - base58則由58個字符組成,一般用在比特幣裡的一種編碼方式,沒有`0`、`o`、`I`、`i`、`+`、`/` - base64原理:base64是3個字節為一組,一共24位元,base64把三個字節轉換為4組,將值控制在0~63之間,如果不夠3個字節則以`=`補齊 ![密碼學_01_base64轉換原理](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_01_base64%E8%BD%89%E6%8F%9B%E5%8E%9F%E7%90%86.png?raw=true) ### toString()和new String()區別 ```java public static void main(final String srg[]) throws Exception { final String base64 = "01Jk6AecyqldguNoHIO7dw==";// 密文 final java.util.Base64.Decoder decoder = Base64.getDecoder(); final byte[] binary = decoder.decode(base64); System.out.println("origin str= " + base64); System.out.println("new String()= " + new String(binary)); System.out.println("toString()= " + binary.toString()); } ``` - `toString()`,實際上調用Object.toString(),若無特別覆寫則返回哈希值(類地址) - `new String()`,根據指定編碼格式進行解碼,若無指定則用虛擬機的編碼格式;若是IOS-8859-1則到ASCII找相應字符 ## 🔐ASCII編碼 字元對應表可參考:[ASCII-wiki](https://zh.wikipedia.org/wiki/ASCII) ```java public static void main(final String args[]) { final String str = "hello"; final char[] charArray = str.toCharArray(); System.out.println(charArray.hashCode()); for (final char temp : charArray) { System.out.println(temp + "\t " + (int) temp); } } ``` ## 🔐凱撒加密 字母移位 ```java public static void main(final String[] args) { // param final String origin = "Hello World"; final int key = -5; // encryp final String encryp = caesarEncryp(origin, key); System.out.println("origin : " + origin); System.out.println("encryp(" + key + ") : " + encryp); // decryp final int decrypKey = getCaesarDecrypLey(encryp); final String decryp = caesarEncryp(encryp, decrypKey); System.out.println("decryp(" + decrypKey + ") : " + decryp); } /** * 凱撒加密 * * @param origin 明文 * @param key 密鑰,位移位數 * @return 加密後字串 */ private static String caesarEncryp(final String originStr, final int key) { final char[] originChars = new String(originStr).toCharArray(); final StringBuilder sb = new StringBuilder(); for (final char c : originChars) { sb.append(Character.toString((char) ((int) c + key))); } return sb.toString(); } /** * 頻率分析解密密鑰 * @param encrypStr 密文 * @return */ private static int getCaesarDecrypLey(final String encrypStr) { // 輸入為空 if (encrypStr.length() == 0) { return 0; } // 計算出現頻率最高的字母 final char[] encripChar = encrypStr.toLowerCase().toCharArray(); final Map<Character, Integer> countMap = new HashMap<Character, Integer>(); for (final char c : encripChar) { final Integer before = countMap.get(c); countMap.put(c, (before == null || before == 0) ? 1 : before + 1); } final char maxChar = countMap.entrySet()// .stream()// .max(Entry.comparingByValue()).get().getKey(); System.out.println("maxChar=" + maxChar + " ;count=" + countMap.get(maxChar)); // 計算與字母e的差距 return (int) 'e' - (int) maxChar; } ``` ## 🔐對稱加密 Symmetric Encryption 一組密鑰同時用作加密和解密 ![密碼學_02_對稱加密](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_02_%E5%B0%8D%E7%A8%B1%E5%8A%A0%E5%AF%86.png?raw=true) - 特點 1. 加密速度快 2. 密文不可逆 3. 若在編碼表上找不到對應字符會出現亂碼 4. 一般需要結合base64一起使用 - class Cipher,提供加解密密碼的功能,構成Java加密擴展(JCE)框架核心,參考:[Cipher -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html) `Cipher cipher = Cipher.getInstance("算法/模式/填充");`,若無指定則為`"DES/ECB/PKCS5Padding"` ### 加解密算法 - DES(Data Encryption Standard),數據加密標準,是一種使用密鑰加密的塊算法 ```java public static void main(final String srg[]) throws Exception { final String input = "Hello World";// 明文 final String key = "12345678";// 密鑰,DES必須為8個字節,AES必須為16字節 final String algrithm = "DES";// 加密算法 final String transformation = algrithm + "/ECB/PKCS5Padding";// 加密類型 final String encrypt = desEncrypt(input, key, transformation, algrithm); System.out.println("EDS Encrypt= " + encrypt); final String decrypt = desDecrypt(encrypt, key, transformation, algrithm); System.out.println("EDS Decrypt= " + decrypt); } /** * 使用DES方式加密 * * @param input 明文 * @param key 密鑰 * @param transformation 加密類型 * @param algrithm 算法 * @return 密文 * @throws Exception */ private static String desEncrypt(final String input, final String key, final String transformation, final String algrithm) throws Exception { final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algrithm);// 加密規則 final Cipher cipher = Cipher.getInstance(transformation);// 加解密對象 cipher.init(Cipher.ENCRYPT_MODE, sks); final byte[] outputBytes = cipher.doFinal(input.getBytes());//加解密操作 // 在編碼表找不到對應的字符,所以直接打印會出現亂碼 // System.out.println(new String(outputBytes)); // binary-->base64 final Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(outputBytes); } /** * 使用DES方式解密 * * @param input base64密文 * @param key 密鑰 * @param transformation 加密類型 * @param algrithm 算法 * @return 明文 * @throws Exception */ private static String desDecrypt(final String input, final String key, final String transformation, final String algrithm) throws Exception { // base64-->binary final java.util.Base64.Decoder decoder = Base64.getDecoder(); final byte[] encodeBytes = decoder.decode(input); final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algrithm);// 加密規則 final Cipher cipher = Cipher.getInstance(transformation);// 加解密對象 cipher.init(Cipher.DECRYPT_MODE, sks); final byte[] outputBytes = cipher.doFinal(encodeBytes);//加解密操作 return new String(outputBytes); } ``` - AES(Advanced Encryption Standard),采用塊口密算法,用來替代DES 做法和DES一樣,只是修改加密類型,而且密鑰必須為16個字節 ### 加密模式 參考:[分組密碼工作模式 -wiki](https://zh.wikipedia.org/wiki/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F) - ECB(Electronic Codebook):電子密碼本,明文分塊後使用==同一組密鑰==獨立加密 ![密碼學_03_ECB加密](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_03_ECB%E5%8A%A0%E5%AF%86.png?raw=true) 1. 特點:同時加密,一樣的原文會加密出一樣的密文 2. 優點:可并行處理數據,加解密速度快 3. 缺點:不能很好的保護數據 - CBC(Cipher-block Chaining):密碼塊連接,明文分塊後與前一個密文塊進行異或後,再進行加密。每個密文塊都依賴於它前面所有的明文塊 ![密碼學_04_CBC加密](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_04_CBC%E5%8A%A0%E5%AF%86.png?raw=true) 1. 優點:同樣的原文生成的密文不一樣 2. 缺點:串行處理數據,加解密速變快 3. 注意,使用需要另外新增IV參數 ```java /** * 使用DES方式加密,CBC加密模式 * * @param input 明文 * @param key 密鑰 * @return 密文 * @throws Exception */ private static String desEncrypt(final String input, final String key) throws Exception { final IvParameterSpec iv = new IvParameterSpec("12345678".getBytes());// iv向量 final SecretKeySpec sks = new SecretKeySpec(key.getBytes(), "DES");// 加密規則 final Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");// 加解密對象 cipher.init(Cipher.ENCRYPT_MODE, sks, iv); final byte[] outputBytes = cipher.doFinal(input.getBytes());//加解密操作 final Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(outputBytes); } ``` ### 填充模式 當需要按塊處理的數據,數據長度不符合塊處理需求時,按照指定方法來填充塊的規則 - NoPadding,不填充 1. 在DES加密下,原文長度必須是8b的整數倍 2. 在AES加密下,原文長度必須是16b的整數倍 - PKCS5Padding,補足數據塊位數 ## 🔐消息摘要算法 Message Digest 也稱散列函數、雜湊演函數(Hash function) - 將長度不固定的信息(或稱消息)作為輸入參數,輸出固定長度的Hash值 - 為了保證輸入值,使用消息摘要計算出的值不可以篡改 - 特性: 1. 計算的資料來源無長度限制 2. 雜湊值長度永遠固定 3. 雜湊演算法為單向運算 4. 不同資料來源不會産出相同的雜湊值 - [在線雜湊産生器](https://tool.puckwang.com/tools/hash/),注意,輸出為16進制的字串 ### Linux中進行雜湊 1. MD5,已破解 ```console [mickey@localhost ~]$ echo "hello"|openssl md5 (stdin)= b1946ac92492d2347c6235b4d2611184 ``` 1. SHA(Secure Hash Algorithm)安全散列算法 2. SHA1,已破解 ```console [mickey@localhost ~]$ echo "hello"|openssl sha1 (stdin)= f572d396fae9206628714fb2ce00f72e94f2258f ``` 1. SHA224 ```console [mickey@localhost ~]$ echo "hello"|openssl sha224 (stdin)= 2d6d67d91d0badcdd06cbbba1fe11538a68a37ec9c2e26457ceff12b ``` 1. SHA256 ```console [mickey@localhost ~]$ echo "hello"|openssl sha256 (stdin)= 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 ``` 1. SHA384 ```console [mickey@localhost ~]$ echo "hello"|openssl sha384 (stdin)= 1d0f284efe3edea4b9ca3bd514fa134b17eae361ccc7a1eefeff801b9bd6604e01f21f6bf249ef030599f0c218f2ba8c ``` 1. SHA512 ```console [mickey@localhost ~]$ echo "hello"|openssl sha512 (stdin)= e7c22b994c59d9cf2b48e549b1e24666636045930d3da7c1acb299d1c3b7f931f94aae41edda2c2b207a36e10f8bcb8d45223e54878f5b316e7ce3b6bc019629 ``` ### Java8中進行雜湊 [MessageDigest -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html) 類型:MD5、SHA-1、SHA-224、SHA-256、SHA-384、SHA-512 ```java public static void main(final String[] args) throws Exception { final String input = "Hello World";// 原文 final String type = "SHA";// 雜湊算法類型 final MessageDigest md = MessageDigest.getInstance(type); final byte[] outputBinary = md.digest(input.getBytes()); // 將輸出binary轉為Base64 final String outputBase64 = Base64.getEncoder().encodeToString(outputBinary); // 將輸出binary轉為16進制 final StringBuilder sb = new StringBuilder(); for (final byte b : outputBinary) { final String hex = Integer.toHexString(b & 0xff); sb.append(hex.length() == 1 ? "0" + hex : hex);// 補齊至兩位 } System.out.println(String.format("original= %s;%s_base64= %s;%s_16= %s", input, type, outputBase64, type, sb)); } ``` ## 🔐非對稱加密 Asymmetric Encryption ![密碼學_05_非對稱加密](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_05_%E9%9D%9E%E5%B0%8D%E7%A8%B1%E5%8A%A0%E5%AF%86.png?raw=true) - 排對稱加密將金鑰分為公鑰及私鑰,公鑰可發布於網上,私鑰則用戶自行保管,因此會出現以下四種加解密情況: 1. 公鑰加密,公鑰解密-->此方法無意義,相當於無加密 2. 公鑰加密,私鑰解密 3. 私鑰加密,公鑰解密 4. 私鑰加密,私鑰解密 - Java相關類 1. [KeyPairGenerator -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyPairGenerator.html),密鑰對生成器 1. [KeyPair -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyPair.html),密鑰對 1. [PublicKey -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/PublicKey.html "interface in java.security") 2. [PrivateKey -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/PrivateKey.html "interface in java.security") 3. [KeyFactory -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/KeyFactory.html),binery-->PublicKey/PrivateKey 4. [PKCS8EncodedKeySpec -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/spec/PKCS8EncodedKeySpec.html),私鑰規范 - 程式參考: 1. [Java加密與解密筆記(三) 非對稱加密](https://www.itread01.com/content/1508482697.html) ### RSA [RSA加密 -wiki](https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95) ```java public static void main(final String[] args) throws Exception { final String algorithm = "RSA";// 密鑰對生成方式 final String input = "Hello World";//明文 System.out.println("origin= " + input); // ! 第一步,取得公鑰和私鑰 genKey(algorithm); // ! 第二步,使用公鑰或私鑰加密 final String ecryptStr = doEcrypt(algorithm, getKeyFromFile(algorithm, "D:\\private.pri"), input); System.out.println("ecrypt= " + ecryptStr); // ! 第三步,使用公鑰或私鑰解密 final String decrypt = doDecrypt(algorithm, getKeyFromFile(algorithm, "D:\\public.pub"), ecryptStr); System.out.println("decrypt= " + decrypt); } /** * 取得公鑰和私鑰 * * @param algorithm 密鑰對生成方式 * @return 公私鑰 * @throws Exception */ private static void genKey(final String algorithm) throws Exception { // 密鑰對生成器 final KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm); // 生成密鑰對 final KeyPair kp = kpg.generateKeyPair(); // 最後結果轉為base64存成文檔 saveKeyFile(kp.getPublic(), kp.getPrivate(), "D:\\public.pub", "D:\\private.pri"); } /** * 儲存公私鑰文檔 * * @param keyMap * @param publicKeyPath 公鑰文檔路徑 * @param privateKeyPath 私鑰文檔路徑 * @throws Exception */ private static void saveKeyFile(final PublicKey publicKey, final PrivateKey privateKey, final String publicKeyPath, final String privateKeyPath) throws Exception { final String encoding = "UTF-8"; final Encoder encoder = Base64.getEncoder(); FileUtils.write(new File(publicKeyPath), encoder.encodeToString(publicKey.getEncoded()), encoding); FileUtils.write(new File(privateKeyPath), encoder.encodeToString(privateKey.getEncoded()), encoding); } /** * 使用公鑰或私鑰加密 * * @param algorithm 加密方式 * @param isPuclic 密鑰是否為公鑰 * @param keyFilePath 密鑰文檔路徑 * @param input 明文 * @return 密文 * @throws Exception */ private static String doEcrypt(final String algorithm, final Key key, final String input) throws Exception { final Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, key); final byte[] outputBinary = cipher.doFinal(input.getBytes()); return Base64.getEncoder().encodeToString(outputBinary); } /** * 使用公鑰或私鑰解密 * * @param algorithm 加密方式 * @param key 公鑰或私鑰 * @param ecryptStr 密文 * @return 明文 * @throws Exception */ private static String doDecrypt(final String algorithm, final Key key, final String ecryptStr) throws Exception { final byte[] decode = Base64.getDecoder().decode(ecryptStr); final Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.DECRYPT_MODE, key); return new String(cipher.doFinal(decode)); } /** * 從檔案取得公鑰或私鑰 * * @param algorithm 加密方式 * @param keyFilePath 公鑰或私鑰路徑 * @return 公鑰或私鑰 * @throws Exception */ private static Key getKeyFromFile(final String algorithm, final String keyFilePath) throws Exception { final String encoding = "UTF-8"; // file --> String --> binery final String keyString = FileUtils.readFileToString(new File(keyFilePath), encoding); final byte[] keyBinery = Base64.getDecoder().decode(keyString); // binery --> PublicKey/PrivateKey final KeyFactory kf = KeyFactory.getInstance(algorithm); Key key = null;// 密鑰 if (keyFilePath.endsWith(".pub")) {// 判斷公鑰或私鑰 final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBinery);// 建立公鑰規則 key = kf.generatePublic(keySpec); } else { final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBinery);// 建立私鑰規則 key = kf.generatePrivate(keySpec); } return key; } ``` ### ECC `TODO` ### 數字簽名 - 定義:公鑰數字簽名,只有信息的發送者才能産生別人無法偽造的一段數字的字串。類似於紙本簽名 - 基本原理: ![密碼學_06_數字簽名流程](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_06_%E6%95%B8%E5%AD%97%E7%B0%BD%E5%90%8D%E6%B5%81%E7%A8%8B.png?raw=true) 1. 發送者將文件進行雜湊以取得信息摘要(數字簽名),并將摘要附在上文件上 2. 發送者將帶摘要的文件用私鑰加密,并發送 3. 接收者用公鑰解密取得帶摘要的文件 4. 接收者對文件進行雜湊以取得的信息摘要和附在文件上的摘要比對,若一致代表此文件未被修改 - Java相關類 1. [Signature -Java8官方文件](https://docs.oracle.com/javase/8/docs/api/java/security/Signature.html) ```java public static void main(final String[] args) throws Exception { final String algorithm = "RSA";// 密鑰對生成方式 final String algorithmForSign = "SHA1with" + algorithm; final String input = "Hello World";//明文 System.out.println("origin= " + input); // ! 第一步,取得公鑰 genKey(algorithm); // ! 第二步,使用私鑰産生數字簽名 final String signStr = doSign(algorithm, algorithmForSign, input, "D:\\private.pri"); System.out.println("signStr= " + signStr); // ! 第三步,將簽名和明文打包并用密鑰加密(一般非對稱加密,故先省略) final String decyptStr = signStr + "_" + input; // ! 第四步,使用公鑰驗證數字簽名 final boolean verifyResult = doVerifySign(algorithm, algorithmForSign, decyptStr, "D:\\public.pub"); System.out.println("verifyResult= " + verifyResult); } private static boolean doVerifySign(final String algorithm, final String algorithmForSign, final String decyptStr, final String publicKeyPath) throws Exception { // transfer signStr to binary final String[] strs = decyptStr.split("_"); final byte[] signBinary = Base64.getDecoder().decode(strs[0]); // get message binary for compare final byte[] messageBinary = strs[1].getBytes(); // get public key final Key key = getKeyFromFile(algorithm, publicKeyPath); // do verify final Signature signature = Signature.getInstance(algorithmForSign); signature.initVerify((PublicKey) key); signature.update(messageBinary); return signature.verify(signBinary); } /** * 取得公鑰和私鑰 * * @param algorithm 密鑰對生成方式 * @return 公私鑰 * @throws Exception */ private static void genKey(final String algorithm) throws Exception { // 密鑰對生成器 final KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm); // 生成密鑰對 final KeyPair kp = kpg.generateKeyPair(); // 最後結果轉為base64存成文檔 saveKeyFile(kp.getPublic(), kp.getPrivate(), "D:\\public.pub", "D:\\private.pri"); } /** * 儲存公私鑰文檔 * * @param keyMap * @param publicKeyPath 公鑰文檔路徑 * @param privateKeyPath 私鑰文檔路徑 * @throws Exception */ private static void saveKeyFile(final PublicKey publicKey, final PrivateKey privateKey, final String publicKeyPath, final String privateKeyPath) throws Exception { final String encoding = "UTF-8"; final Encoder encoder = Base64.getEncoder(); FileUtils.write(new File(publicKeyPath), encoder.encodeToString(publicKey.getEncoded()), encoding); FileUtils.write(new File(privateKeyPath), encoder.encodeToString(privateKey.getEncoded()), encoding); } /** * 使用私鑰産生數字簽名 * * @param algorithm 密鑰對生成方式 * @param algorithmForSign 簽名生成方式 * @param input 明文 * @param privateKeyPath 密鑰路徑 * @return 電子簽名 * @throws Exception */ private static String doSign(final String algorithm, final String algorithmForSign, final String input, final String privateKeyPath) throws Exception { // get public key or private key final Key key = getKeyFromFile(algorithm, privateKeyPath); // do sign final Signature signature = Signature.getInstance(algorithmForSign); if (privateKeyPath.endsWith(".pub")) { System.out.println("please use private key"); } else { signature.initSign((PrivateKey) key); } signature.update(input.getBytes()); return Base64.getEncoder().encodeToString(signature.sign()); } /** * 從檔案取得公鑰或私鑰 * * @param algorithm 加密方式 * @param keyFilePath 公鑰或私鑰路徑 * @return 公鑰或私鑰 * @throws Exception */ private static Key getKeyFromFile(final String algorithm, final String keyFilePath) throws Exception { final String encoding = "UTF-8"; // file --> String --> binery final String keyString = FileUtils.readFileToString(new File(keyFilePath), encoding); final byte[] keyBinery = Base64.getDecoder().decode(keyString); // binery --> PublicKey/PrivateKey final KeyFactory kf = KeyFactory.getInstance(algorithm); Key key = null;// 密鑰 if (keyFilePath.endsWith(".pub")) {// 判斷公鑰或私鑰 final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBinery);// 建立公鑰規則 key = kf.generatePublic(keySpec); } else { final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBinery);// 建立私鑰規則 key = kf.generatePrivate(keySpec); } return key; } ``` ### 數字證書 - 數字證書相當於第三方身分認證,類似於雅思、托福證書,參考: 1. 整個流程可參考[[🌐網路架構_05_HTTPS協議]] 2. 參考:[數字證書圖解流程](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/723427/) ![密碼學_07_數字證書](https://github.com/MickeyHuang233/CodingStudyNote/blob/main/05_%E8%B3%87%E5%AE%89%E6%8A%80%E8%A1%93/%F0%9F%94%90%E5%AF%86%E7%A2%BC%E5%AD%B8/images/%E5%AF%86%E7%A2%BC%E5%AD%B8_07_%E6%95%B8%E5%AD%97%E8%AD%89%E6%9B%B8.png?raw=true)