2012年6月28日 星期四

Desire Z 刷 ICS ROM

原本刷 R6 的 ROM,用 Android 2.3.5 + Sense 3.5
不過用起來實在太頓了,所以後來想說乾脆放棄 HTC Sense,找看看別的 ROM
因此這次選上了今天才剛進入 RC 的 CyanogenMod 9.0.0-RC1

圖片來源:xda-developers

2012年6月27日 星期三

在 Windows 底下跑 Tomcat

參考資料:在Windows 安裝 Tomcat 6 測試站

在 Windows 底下安裝 Tomcat,真正要做的只有:
1、安裝 JDK
2、設定環境變數
3、下載並執行 Tomcat

byte array 與 hexadecimal 字串互相轉換

記錄這篇是因為加解密的結果會是 byte array,但是我的資料必須透過 HTTP 協定傳遞
所以必須把 byte array 轉換成字串,傳送出去之後還要能再轉回原本的 byte array 來解密。
根據 Encrypting and decrypting large data using Java and RSA 的建議
要把 byte array 轉成 hexadecimal 的字串,在網路上傳送才不容易有問題。

參考資料:
1、Converting A String To Hexadecimal In Java
2、How to convert Hex String to Bytes and viceversa in Java?

2012年6月26日 星期二

在 Java 使用加密演算法(三):使用 RSA 加解密長資料

在前一篇文章「在 Java 使用加密演算法(二)」中雖然已經有用 RSA 加解密的程式碼
但那段程式碼沒有辦法加解密長度稍長的資料(大概是 100 多個 bytes 以上)
稍微查詢了一些資料,目前個人的了解應該是受限於 RSA 是個 block cipher
block cipher 本身的設計就是一次只用來加解密很小的資料。
網路上雖然討論在 Java 中使用 RSA 的文章很多
但多數似乎都假定看的人應該要知道 RSA 的 "一次加密" 只能加密很短的資料,就很少在文章中提到這件事....。
PS. "一次加密" 指的就是執行一次 RSA 的動作。

2012年6月25日 星期一

在 Java 使用加密演算法(二):使用 RSA 加密與解密(最後修改:2015-10-11)

利用 RSA 加密演算法來加密字串的範例如下~
這裡總共分成兩段程式碼,分別是 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 加密步骤解释