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 加密步骤解释

2012年5月11日 星期五

申請 Amazon Appstore 的帳號

Amazon Appstore 有每日免費的 App 可以下載~。

嗯,一句話點明為什麼會想要申請 Amazon Appstore 的帳號 XD
之前曾經有中斷好一陣子,因為 Amazon 限制一定要美國的 IP 才能購買
今天突然發現好像限制又取消了?

2012年5月10日 星期四

deluge 無法開啟的解決方法

這兩天我的 ubuntu 從 11.10 升級到 12.04 LTS
然後才突然發現我的 deluge 不知道什麼時候開始掛掉了
重新安裝 deluge 和 deluged 好幾次都沒有解決
狀況是打開 deluge 介面後,畫面的左右邊都一片空白,然後連線管理員開啟常駐後也連不上
搞了好一陣子,最後找到這篇文章:

2012年5月8日 星期二

2012年5月1日 星期二

(暫存) Google Geocoding API

Google Geocoding API
https://developers.google.com/maps/documentation/geocoding/?hl=zh-TW

地理編碼是指將地址 (例如 1600 Amphitheatre Parkway, Mountain View, CA) 轉換成地理座標 (例如緯度 37.423021 和經度 -122.083739) 的程序,而您可以使用這些座標來放置標記或設定地圖位置。Google Geocoding API 可讓使用者直接透過 HTTP 要求存取 Geocoder。另外,此服務能讓您執行反向作業 (將座標轉換為位址);此程序也就是「反向地理編碼」。

Google Place API
1、Android Development – Part 1: Using Google’s Places API to Develop Compelling Location Based Mobile Applications
2、Get list of places surrounding user's current location

找出以標記為中心一公里之內的地點

這個問題其實是在看論壇時看到的
因為自己之後要寫的東西也會用到,所以就先來查了一下
不過一查才發現這是相當複雜的問題呀 XD

(圖片取自維基百科)

座標跟距離的問題有分成兩種:
1、知道兩點的座標,如何求出兩點之間的距離(以公尺或者公里為單位)
2、以某點為中心點,附近有很多座標不同的其他點,如何得知距離在 XXX 公尺/公里之內的點有哪些

1、知道兩點的座標,如何求出兩點之間的距離(以公尺或者公里為單位)

第一點在網路上討論好像比較多,主要問題在於地球是個球體
所以在緯度 0 度的地方,經度移動 1 度所走的實際距離,跟在緯度 60 度的地方移動 1 度經度所走的距離實際上是不同的。

Google Map API 入門教學(二):在 Map 上加入標記

要在 Google Map 上加入標記,需要利用的是 google.maps.Marker 這個物件。
延續 Google Map API 入門教學(一)的例子,可以將 JavaScript 加上以下的語法:

google.maps.event.addListener(currentMap, "click", function(event) {
  // Setting of marker
  var optionOfMarker = {
    position: event.latLng,
    map: currentMap,
    title: event.latLng.toString()
  };

  // Show marker in the place of mouse clicks
  mapMarker = new google.maps.Marker(optionOfMarker);
});