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:本部落格的內容授權請參閱部落格底部的授權宣告。
2018年9月3日 星期一
2018年8月27日 星期一
2014年7月7日 星期一
使用 AES 加密後的資料長度
不過因為 AES 是 block cipher,要加密的對象必須是 16 bytes 的倍數才能正常加密
因此演算法必須搭配 padding 的方法,而 padding 的方法就會影響加密後的長度。
從 [1] 的回應來看,如果 padding 的方法是使用 PKCS5 或 PKCS7 的話,加密後的長度如下:
cipherLen = (clearLen/16 + 1) * 16;
參考資料:
1、Size of data after AES encryption
2、How does PKCS#7 padding work with AES-256, CBC mode?
2013年7月31日 星期三
儲存密碼的方法:Java 的 Scrypt KDF 演算法
大概是說一般常見的密碼儲存方法是使用 One-Way Hash Function 來把密碼變成一串亂碼,儲存在系統上
但不管使用哪種雜湊演算法,其實都不夠安全~主要原因來自於雜湊演算法的兩點特徵:
2013年7月12日 星期五
2013年7月11日 星期四
在 Java 使用加密演算法(六):KeyGenerator、Cipher、工作模式與填充方式之間的關係
keyGenerator 是用來產生 Key 的產生器,取得 KeyGenerator 的實體必須透過 getInstance() 方法,同時需要輸入使用的演算法的名稱。
Cipher 則是用來進行加密、解密的工具,取得 Cipher 的實體也必須透過 getInstance() 方法,同時輸入要使用的演算法名稱。
2013年7月8日 星期一
在 Java 使用加密演算法(五):使用 RSA 加解密長資料的另一種實作方法
但不知道為什麼突然跑出 javax.crypto.BadPaddingException: Data must start with zero 的 Exception
找來找去都不知道問題出在哪.....後來查到 [1] 時發現好像有比較簡潔的寫法...。
2013年7月5日 星期五
在 Java 使用加密演算法(四):使用 Jasypt 與 Bouncy Castle 產生雜湊
不過基於希望程式碼簡潔、容易呼叫,並且能夠支援更多的加密演算法,所以還是嘗試找了一下
根據在 StackOverflow 社群上的問答 [1],在這裡嘗試了使用 Jasypt 與 Bouncy Castle 作為產生雜湊碼的方法。
2013年5月31日 星期五
2012年6月26日 星期二
在 Java 使用加密演算法(三):使用 RSA 加解密長資料
但那段程式碼沒有辦法加解密長度稍長的資料(大概是 100 多個 bytes 以上)
稍微查詢了一些資料,目前個人的了解應該是受限於 RSA 是個 block cipher
block cipher 本身的設計就是一次只用來加解密很小的資料。
網路上雖然討論在 Java 中使用 RSA 的文章很多
但多數似乎都假定看的人應該要知道 RSA 的 "一次加密" 只能加密很短的資料,就很少在文章中提到這件事....。
PS. "一次加密" 指的就是執行一次 RSA 的動作。
2012年6月25日 星期一
在 Java 使用加密演算法(二):使用 RSA 加密與解密(最後修改:2015-10-11)
這裡總共分成兩段程式碼,分別是 Main.java 以及 myEncryption.java
Main.java 是主要程序;myEncryption.java 進行加解密。
2012年6月19日 星期二
在 Java 使用加密演算法(一):產生與儲存 RSA 的金鑰(2017-07-19 調整)
正在嘗試要實作公開金鑰的加密演算法,目前搜尋了一些資料,原本有在疑惑應該選用哪個演算法
不過在搜尋到 [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:
- main():程式的主要進入點,除了產生金鑰以外,也會負責呼叫其他 method。
- printKeyPair():單純為了將產生的金鑰印出。而因為金鑰會是以 byte 形式存在的東西,因此必須透過某些編碼轉成能夠被印出來的字串。這裡是使用 Base64 編碼。
- saveKeyPair():將金鑰分別輸出到指定路徑底下,並產生 “public.key” 和 “private.key” 兩個檔案。
- loadKeyPair():從指定路徑中尋找 “public.key” 和 “private.key” 兩個檔案,並將他們載入成 KeyPair 型態到程式中。
以下就分別是各個 method 的原始碼以及相關的說明。
main()
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)
然後再從檔案中把金鑰讀進來,再印出來,可以用眼睛確認跟輸出前是否長得一樣。
printKeyPair()
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 編碼器,把金鑰的內容輸出成文字印出來而已。
saveKeyPair()
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。
loadKeyPair()
// 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
透過 RSA 加密與解密
請參考下篇文章:在 Java 使用加密演算法(二):使用 RSA 加密與解密
附錄:對稱金鑰加密(symmetric-key algorithm)
原本弄錯了以為 AES 也是屬於公開金鑰加密,不過後來查到的程式碼覺得怪怪的
於是仔細查了一下 AES,發現它應該是對稱金鑰加密的演算法才對~(參閱下面參考資料中的連結 6)
也就是發送端與接收端共用同一個 secret key。
AES 破解的資訊可參考:New Attack on AES
微軟在 2011 年 8 月發現可以加快破解 AES 的方法,例如以 AES-128 來說,可以將複雜度降低到 2126.1
不過其實 2126.1 還是很多 XD,也就是目前來說 AES 還是安全性可接受的加密方法~。
參考資料:
6、AES encryption, what are public and private keys?
7、Using AES with Java Technology
8、【java】AES加密解密|及Base64的使用
9、Java AES Encrypt & Decrypt Example(加解密)
10、JAVA AES 加密步骤解释