不過用起來實在太頓了,所以後來想說乾脆放棄 HTC Sense,找看看別的 ROM
因此這次選上了今天才剛進入 RC 的 CyanogenMod 9.0.0-RC1
圖片來源:xda-developers
Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification. Junior programmers create simple solutions to simple problems. Senior programmers create complex solutions to complex problems. Great programmers find simple solutions to complex problems. 註1:本部落格的範例程式碼在 2015 年以前的文章中,大多是以全型空白做縮排。如需服用,請自行用文字編輯器的取代功能把全型空白取代成半型空白。
- Bertrand Meyer
- Charles Connell
註2:本部落格的內容授權請參閱部落格底部的授權宣告。
正在嘗試要實作公開金鑰的加密演算法,目前搜尋了一些資料,原本有在疑惑應該選用哪個演算法
不過在搜尋到 [4] 以後,就決定還是用 RSA 了。
參考資料
1、JAVA 上加密演算法的實作範例
2、Generate Public and Private Keys
3、Save/Load Private and Public Key to/from a file
4、RSA Versus DSA and EL GAMAL
5、有關如何安全地儲存 key 的討論,可以看看:How to securely store a PrivateKey in code
這裡分成三個部分來討論,首先是要如何在 Java 裡面產生 RSA 的 Public Key 和 Private Key
以及把產生的兩把 Key 都儲存成檔案,再把它從檔案裡讀出來,最後才是真正把資料加密和解密。
生成金鑰和讀寫金鑰可參考以下的程式碼,基本的程式碼是參考 [3],只有做小幅度的修改而已。
程式碼分為以下幾個 method:
以下就分別是各個 method 的原始碼以及相關的說明。
public static void main(String[] args) throws Throwable { String path = "D:/encrypt"; SecureRandom random = new SecureRandom(); random.setSeed("test".getBytes()); // Generate the key pair (public key and private key). KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA"); // Specify that the key should be 1024-bit. keygen.initialize(1024, random); KeyPair generatedKeyPair = keygen.generateKeyPair(); // Print the key. The key will be encoded by Base64. log.trace("Generated key pair:"); printKeyPair(generatedKeyPair); // Store the key as files. log.info("Output the key pair to {}", path); saveKeyPair(path, generatedKeyPair); // Load the keys log.info("Loaded Key Pair from {}", path); KeyPair loadedKeyPair = loadKeyPair(path, "RSA"); // Load the keys from files // Print the loaded key pair to ensure that they are exactly the same. log.trace("Loaded key pair:"); printKeyPair(loadedKeyPair); }
4~5 行是用指定的字串去產生金鑰,透過這個方法的話,只要指定的字串是同一個,產生的金鑰就會是同一個。
這裡指定的字串是 "test"。
7~11 行是產生金鑰的程式碼,其中第 10 行的 1024 是指要產生的金鑰長度為 1024-bit
長度設得越長,產生金鑰所需要的時間也越長~
我的電腦產生 512 bits 的金鑰一瞬間就出來了,1024-bit 要大概一兩秒
2048-bit 就要稍微等個三秒之類的吧...(秒數是純感覺,沒有做過任何精準的測試)
11 行將 KeyPair 產生出來,也就是同時產生 Private Key 和 Public Key,會被存在同一個物件裡。
然後後面就依序是先把產生的金鑰印出來,接著把金鑰輸出成檔案
(預期輸出位置是 D:\encrypt\public.key 和 D:\encrypt\private.key)
然後再從檔案中把金鑰讀進來,再印出來,可以用眼睛確認跟輸出前是否長得一樣。
public static void printKeyPair(KeyPair keyPair) { log.trace(" public key: {}", Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded())); log.trace(" private key: {}", Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded())); }
這裡很單純,就只是利用 Java 原生支援的 Base64 編碼器,把金鑰的內容輸出成文字印出來而已。
public static void saveKeyPair(String path, KeyPair keyPair) throws IOException { PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // Store Public Key. File fileForPublicKey = Paths.get(path, "public.key").toFile(); log.trace("Public key will be output to '{}'.", fileForPublicKey); X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded()); try (FileOutputStream fos = new FileOutputStream(fileForPublicKey)) { fos.write(x509EncodedKeySpec.getEncoded()); } // Store Private Key. File fileForPrivateKey = Paths.get(path, "private.key").toFile(); log.trace("Private key will be output to '{}'.", fileForPrivateKey); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); try (FileOutputStream fos = new FileOutputStream(fileForPrivateKey)) { fos.write(pkcs8EncodedKeySpec.getEncoded()); } }
5 ~ 7 行是產生路徑,實際執行起來,這裡產生的路徑會是 D:\encrypt\public.key。
9 ~ 12 行是把 Public Key 的內容輸出到檔案 D:\encrypt\public.key。
同樣地,接著 15 ~ 16 行也是產生路徑,實際執行時這個路徑會是 D:\encrypt\private.key。
18 ~ 21 行則是把 Private Key 的內容輸出到檔案 D:\encrypt\private.key。
// Load the keys from files. public static KeyPair loadKeyPair(String path, String algorithm) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { // Initiate the factory with specified algorithm. KeyFactory keyFactory = KeyFactory.getInstance(algorithm); // Read public key from file. File fileForPublicKey = Paths.get(path, "public.key").toFile(); log.trace("Public key will be loaded from '{}'.", fileForPublicKey); PublicKey publicKey = null; try (FileInputStream fis = new FileInputStream(fileForPublicKey)) { byte[] loadedBytes = new byte[(int) fileForPublicKey.length()]; fis.read(loadedBytes); X509EncodedKeySpec spec = new X509EncodedKeySpec(loadedBytes); publicKey = keyFactory.generatePublic(spec); } // Read private key from file. File fileForPrivateKey = Paths.get(path, "private.key").toFile(); log.trace("Private key will be loaded from '{}'.", fileForPrivateKey); PrivateKey privateKey = null; try (FileInputStream fis = new FileInputStream(fileForPrivateKey)) { byte[] loadedBytes = new byte[(int) fileForPrivateKey.length()]; fis.read(loadedBytes); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(loadedBytes); privateKey = keyFactory.generatePrivate(privateKeySpec); } return new KeyPair(publicKey, privateKey); }
這裡程式碼看起來好像比較長,但其實邏輯跟前一段 saveKeyPair() 沒什麼差別
就是組成路徑,然後讀檔案,再把讀入的 bytes 轉成金鑰的物件。因為流程上大同小異,這裡就先忽略細節了 XD
以上述的 method 全部放在同一個 Class 合併起來執行的話,會跑出類似下面這樣的 log 輸出:
2017-07-19 22:58:44,252 | Generated key pair: 2017-07-19 22:58:44,254 | public key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvIaeWOTOx0aBWaz+fZNwsbork4e5RW94CbWR9rOtNE2jtKmgC6hPpWz7ANxzGL7jie9D/BpziyuSmhd3aquf4rmftiNdoTp55earfArlBO2D8Z0vaGnEUetDcahn0UD1bCekRC93iLzRNN73dm1T0CynFFrvFVA/CLgS/BvWEZwIDAQAB 2017-07-19 22:58:44,260 | private key: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAK8hp5Y5M7HRoFZrP59k3CxuiuTh7lFb3gJtZH2s600TaO0qaALqE+lbPsA3HMYvuOJ70P8GnOLK5KaF3dqq5/iuZ+2I12hOnnl5qt8CuUE7YPxnS9oacRR60NxqGfRQPVsJ6REL3eIvNE03vd2bVPQLKcUWu8VUD8IuBL8G9YRnAgMBAAECgYAMiyaLtfEj7VXEms3lxr2WWRyNpDkDjsbp+ZfXAImh7Z/4TK9Cdi2S6zwlXE0tTMG7Rw8DFSArhki2PKRVQyR2Jvp1m7v3ybwLSbP1GH9APKvfDZvjlNobzTPyut5W7jHFDVcAzSQujFcROKWG4gZk+CF9ywatmm3QaAIpiMgz8QJBAN3RlDj8OdzEcyoUNeYmszMWNEPJjoYMiiwrBYN7GCM5ZhK5fRF67PO38Y//PN1Ve5ViwGlKjyo9cRvIlVyRnnsCQQDKHlV71jCkeMherp0WqdtfAExd0BwzVR//CVHktTtCrutOcXOvEiZlnB92ExPzRCJDCD/gkqnU/h6UOM5tUoQFAkB95RFHNoBwuF7UpxvgQF68xAFt59uoYT2ay+AZO6f7dfxk7Dn7zdTmjqPfonGc/YNiyeWC3PpccvrbVgDPxSY5AkBF7wD8/DuQbQpHWHuaH+N7l4rU2vEnAck0YXEohVyf0g4w8iho5wrKFZ79J9S7U1PXhb80YQrKW7MQ7ibexLJRAkA5TcwKY9IEnYZcQlHI2ZZ0GZEL6TANxMXcFzr3OIk+3CHxBw0FKiG56zussDuRGdopey4KUEsCW19HupLsZamE 2017-07-19 22:58:44,260 | Output the key pair to D:/encrypt 2017-07-19 22:58:44,261 | Public key will be output to 'D:\encrypt\public.key'. 2017-07-19 22:58:44,262 | Private key will be output to 'D:\encrypt\private.key'. 2017-07-19 22:58:44,263 | Loaded Key Pair from D:/encrypt 2017-07-19 22:58:44,263 | Public key will be loaded from 'D:\encrypt\public.key'. 2017-07-19 22:58:44,264 | Private key will be loaded from 'D:\encrypt\private.key'. 2017-07-19 22:58:44,264 | Loaded key pair: 2017-07-19 22:58:44,264 | public key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvIaeWOTOx0aBWaz+fZNwsbork4e5RW94CbWR9rOtNE2jtKmgC6hPpWz7ANxzGL7jie9D/BpziyuSmhd3aquf4rmftiNdoTp55earfArlBO2D8Z0vaGnEUetDcahn0UD1bCekRC93iLzRNN73dm1T0CynFFrvFVA/CLgS/BvWEZwIDAQAB 2017-07-19 22:58:44,264 | private key: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAK8hp5Y5M7HRoFZrP59k3CxuiuTh7lFb3gJtZH2s600TaO0qaALqE+lbPsA3HMYvuOJ70P8GnOLK5KaF3dqq5/iuZ+2I12hOnnl5qt8CuUE7YPxnS9oacRR60NxqGfRQPVsJ6REL3eIvNE03vd2bVPQLKcUWu8VUD8IuBL8G9YRnAgMBAAECgYAMiyaLtfEj7VXEms3lxr2WWRyNpDkDjsbp+ZfXAImh7Z/4TK9Cdi2S6zwlXE0tTMG7Rw8DFSArhki2PKRVQyR2Jvp1m7v3ybwLSbP1GH9APKvfDZvjlNobzTPyut5W7jHFDVcAzSQujFcROKWG4gZk+CF9ywatmm3QaAIpiMgz8QJBAN3RlDj8OdzEcyoUNeYmszMWNEPJjoYMiiwrBYN7GCM5ZhK5fRF67PO38Y//PN1Ve5ViwGlKjyo9cRvIlVyRnnsCQQDKHlV71jCkeMherp0WqdtfAExd0BwzVR//CVHktTtCrutOcXOvEiZlnB92ExPzRCJDCD/gkqnU/h6UOM5tUoQFAkB95RFHNoBwuF7UpxvgQF68xAFt59uoYT2ay+AZO6f7dfxk7Dn7zdTmjqPfonGc/YNiyeWC3PpccvrbVgDPxSY5AkBF7wD8/DuQbQpHWHuaH+N7l4rU2vEnAck0YXEohVyf0g4w8iho5wrKFZ79J9S7U1PXhb80YQrKW7MQ7ibexLJRAkA5TcwKY9IEnYZcQlHI2ZZ0GZEL6TANxMXcFzr3OIk+3CHxBw0FKiG56zussDuRGdopey4KUEsCW19HupLsZamE