顯示具有 參考資料 標籤的文章。 顯示所有文章
顯示具有 參考資料 標籤的文章。 顯示所有文章

2009/10/04

One-Time Password

沒想像中的那麼容易,為了簡化文章,我直接將運作流程列出來:

  1. 先在「安全環境」(如你家電腦)設定產生OTP的各項參數: 密碼, Seed, 演算法, Count/Seq.,產生OTP密碼表格
  2. 到「非安全環境」(如網咖、學校)登入時,系統會要求你查找密碼表格的某一個密碼(例如第49個)
  3. 以查表法查出正確密碼,輸入後登入。如果不想查表,可以用 OTPGen/Mobile-OTP 等 Java程式,給予當初初始化的參數,算出表格再查表輸入

這是正統的 RFC2289 一次性密碼系統的流程,很明顯在我們的系統用這種東西根本不對。

因為教授說用預先定義好的內容作數位簽章,很容易被中間人攻擊,所以希望這個內容能夠隨機產生,最好是一次性的,這樣中間人攔截內容要重送時已經失效了。

不過正規的OTP系統,是寫死的流程,跟我們單純要求的隨機內容有點差距。所以又只能自己動手了。

因為我們要求的隨機密碼不會有讓使用者手動輸入的機會,得到後就立刻數位簽章送給伺服器認證,不必擔心太過複雜難以輸入的問題。我的想法是這樣的:

登入系統向伺服器要求(https)一次性隨機密碼
↓
伺服器回傳(https),同時紀錄於 Session 內
↓
登入系統收到密碼,簽章傳送給伺服器(https)
↓
伺服器拿出使用者憑證跟 Session 紀錄的一次性隨機密碼,進行驗證。不論成功與否皆清除 Session 密碼

原先教授的疑慮是這樣的:當我用固定的內容(如: 「超爽的,撿到一百塊咧」或者是當天日期),系統送出給伺服器的過程中假如被攔截(假設 https 還會被攔截解密的話啦...),中間人可以進行重送攻擊 (因為簽章驗證會通過,驗證是驗證資料不是送的人)。如果是固定文字,那此攻擊可以不限時間成功;當天日期的話,當天重送攻擊都有效。所以教授才會說要將內容加到分鐘,甚至是秒,以避免這種攻擊。

改成隨機內容的話,由於使用者要登入都得先向伺服器要求產生一次性隨機密碼,這個密碼萬一真被中間人竊取成功,中間人並沒有私鑰可以進行簽章,所以攻擊失敗。要是在送出簽章的時候攔截實施重送攻擊,一來中間人並沒有要求伺服器發給一次性密碼,伺服器也就沒有用Session儲存,在進行檢查的時候就沒有東西可以用,而紀錄警告;二來這個一次性隨機密碼已經在剛才的正規使用者登入時,就已失效,所以還是沒有用。

如果中間人夠厲害,一次性隨機密碼、數位簽章都取得,再加上入侵伺服器將 Session 改成一次性隨機密碼,那就有機會。但是這需要攻擊 HTTPS, 伺服器,自有難度。Session 變造內容的攻擊法還沒有聽說過,因為 Session 存在伺服器除非入侵不然改不到。只有 Session ID 攻擊 (參見: Session Hijacking 攻擊與Cookie 欺騙(4/5) - 網路攻防戰):利用 Session ID 綁 Cookie 的設計,竊取正規使用者 Cookie,把自己的 Cookie 改的跟正規者一樣,Session ID自然一樣,對應的 Session 正是合法使用者的。

解決方式是每次要求就改變 Session ID,PHP 有函式 session_regenerate_id() 可以避免這種問題。

結論:根基於 HTTPS、一次性隨機密碼、數位簽章、Session 跟每次改變 Session ID的方式,讓中間人的破解難度增加,我想可以達到安全。

2009/08/30

PKI Research #4.5 - Digital Signature 再臨

最近要實作檔案傳輸的部分,由於一開始作使用者登入時,驗證的部分是在本機端 (Client) 作的,伺服器端 (Server) 根本無法得知真實性。所以當有人聲稱某甲要求檔案或傳輸檔案過來時,我們也不能肯定他就是本人。因此,有必要在伺服器端再作一遍。

數位簽章的實作,其實在研究 #4 中已經有了,但是主要是驗證簽章的部分,要在伺服器中執行。搭配接收 POST 上傳檔案的設計,我採用 PHP 來實作。查了一下 PHP 也有 OpenSSL 套件支援 (API),所以我只要使用它提供的函式就可以驗證簽章,關鍵函式 openssl_verify。

openssl_verify 能接受一些參數 (原訊息、數位簽章、公鑰) 來驗證簽章正確性,其中簽章演算法只支援到 SHA1,沒有先前研究的 SHA256,只好把簽章的部分也改用 SHA1 以配合。公鑰的部分,得先從憑證裡抽出來,這有 openssl_get_publickey 可以使用。

大概像這樣:
<?php
$message = 'test'; // Original Message

try {
    $dbh = new PDO('mysql:dbname=FAST;host=127.0.0.1', 'user', 'pass');
} catch (PDOException $e) {
    echo 'Connection failed: ' . $e->getMessage();
}

$sth = $dbh->prepare('SELECT `Public_Key` FROM XXX WHERE ID = ?');
$sth->bindParam(1, $tagID, PDO::PARAM_STR, 8);
$sth->execute();

$row = $sth->fetch();
$pubkeyid = openssl_get_publickey($row['Public_Key']);

// state whether signature is okay or not
$ok = openssl_verify($message, base64_decode($_POST['sigData']), $pubkeyid);
if ($ok == 1) {
    echo "good";
} elseif ($ok == 0) {
    echo "bad";
} else {
    echo "ugly, error checking signature";
}

// free the key from memory
openssl_free_key($pubkeyid);
?>

sigData 是數位簽章內容,為了傳輸方便已先行以 BASE64 編碼,所以這邊再解碼還原。

實際測試時,發現結果一直回傳 bad 即簽章不符,因為 C# 實作驗證都一切正常,所以我懷疑是不是 PHP 的問題,改換使用 JSP 來實作簽章驗證的部分,結果一樣失敗,不同的是 Java 跳出簽章結構錯誤的訊息,我開始懷疑起實作簽章的部分了。在 PHP 上使用 key 私鑰檔實作簽名,跑出來的東西跟 C# 的完全不一樣。C# 簽章驗證的結果,兩種結果都能通過驗證,不過 C# 的結果丟給 PHP 是失敗的,百思不得其解。

回頭想想,會不是會當初實作簽章的時候,想法錯了呢?還記得 .NET Framework 不許我們使用公鑰解密,取而代之的提供 Verify 方法,找看看 Bouncy Castle 有沒有提供類似的 Sign 方法,結果真的有。
public static byte[] signature(byte[] data){
try{
AsymmetricCipherKeyPair keyPair;
using(var reader = File.OpenText(@"D:\Programs\OpenSSL\user1.key")) { // Direct read pem format RSA private key file
keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject();
}

RsaDigestSigner rds = new RsaDigestSigner(new Sha1Digest());
rds.Init(true, keyPair.Private);
rds.BlockUpdate(data, 0, data.Length);
return rds.GenerateSignature();
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}

public static bool verify(byte[] message, byte[] sig){
var crt = new X509Certificate2(@"D:\Programs\OpenSSL\user1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

return rsa.VerifyData(message, "SHA1", sig);
}

Bouncy Castle 提供了 Org.BouncyCastle.Crypto.Signer 類別來處理數位簽章,RsaDigestSigner 就是給 RSA 公開金鑰演算法使用的數位簽章處理類別,由於 PHP 方的 Hash 演算法只支援到 SHA1,所以我簽章的部分也僅使用 SHA1。使用這個 Signer 類別不必自己手動 Hash 訊息,交給類別去作。

驗證簽章的部分跟上次基本上沒很大的差別,只是我也捨棄 VerifyHash 方法改使用 VerifyData 方法,直接把訊息 Hash 的動作交給了方法去處理,我完全不必實作相關 Hash 方法。

這次修改後,產生簽章的結果跟 PHP 的一模一樣,這樣就可以由 C#產生簽章,由 PHP 接收來驗證,進而確認使用者身份,伺服器端也可以利用 SESSION 暫時紀錄結果,就不必每次都要驗證。預設 30 分鐘 SESSION 會過期,到時候再驗證一次即可。

2009/08/18

PKI Research #5 - 使用 OpenCA 產生的憑證跟加密過的私鑰

本篇是研究最卡的部分,足足卡了三天。欲知原因以下見曉。(關鍵字:PKCS#8, PKCS#5)

因為伊達說已經完成研究 OpenCA 發憑證的部分,因此我們使用 OpenCA 的最大目的「產生憑證、私鑰檔和 CRL 憑證廢止清冊」就已經達成,再來就是要研究如何拿來用。

OpenCA 主要使用 MySQL 來儲存相關資料,舉凡憑證申請、憑證存放、CA 憑證、CRL 憑證廢止清冊等全部都在資料庫內,而不是一般的檔案。以 phpMyAdmin 登入後可以看到。





最重要的就是 certificate 資料表了,data 欄位就是重點。裡面存放著憑證以及私鑰檔,我們只要取出來應該就可以利用了吧。我當初是這麼想,但是他的私鑰檔卻經過 DES 加密 (「私鑰經過 PKCS#5 密碼加密標準 "PBEwithMD5andDES-CBC" 加密過的」PKCS#8 私鑰格式標準),檔案格式看起來像這樣:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBgTAbBgkqhkiG9w0BBQMwDgQIjgXxhGkmgGcCAggABIIBYEXZfHjLUnXJ8h+F
...
mtuOc9U=
-----END ENCRYPTED PRIVATE KEY-----


看得出跟一般 OpenSSL 產生的未加密的私鑰檔不同吧,如下 (純粹私鑰,非 PKCS#8 格式):

-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/Qibv
...
Ft4zOc8TnbXnGxXRfGVG0RZfXmxcQNv2yX2G4Vc=
-----END RSA PRIVATE KEY-----


如果以為 PKCS#8 私鑰 key 檔能跟 OpenSSL 的私鑰 key 檔一樣讀取操作 (PKI Research #3 - RSA 加解密),那你就錯了。你會發現 keyPair 永遠是 null,因為察看原始碼才發現,PemReader 沒有特別處理 BEGIN (ENCRYPTED) PRIVATE KEY 的加密私鑰檔 (90 行 switch 開始),更簡單的說法就是 PemReader 不支援 PKCS#8 格式私鑰檔,所以只好自己找方法了。

由於中文極度缺乏相關資源,英文的資源雖然有找到類似的討論串,但也是跟我有著一樣的疑問,而沒有人回答。例如:



其實還有很多,但是我一時忘了。嘗試用 PBEwithMD5andDES-CBC 當關鍵字知道 Bouncy Castle 的 Org.BouncyCastle.Security.PbeUtilities 有支援,但是它的說明手冊不夠完整 (或者該說,沒這種玩意,只有 API),到底是怎樣的使用法也沒人提到。於是我們就來看 PbeUtilities.cs 的原始碼吧:

如果你想知道為什麼是 PKCS#5 PBEwithMD5andDES-CBC 加密格式,用 OpenSSL asn1parse -in 檔案路徑 就可以查看 ASN.1 結構,就會寫到。


原始碼果然定義一堆加密的方法,這下子有希望了。看了一下大概是像這樣子使用:
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;

const String pemp8header = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const String pemp8footer = "-----END ENCRYPTED PRIVATE KEY-----";
char[] password = "xxxxxx".ToCharArray(); // 解密密碼

// 取出檔案中 BASE64 編碼的部分,還原
StringBuilder sb = new StringBuilder(File.ReadAllText(@"B:\tmp.key"));
sb.Replace(pemp8header, "");
sb.Replace(pemp8footer, "");


String pubstr = sb.ToString().Trim();
byte[] binkey = Convert.FromBase64String(pubstr);
var info = EncryptedPrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(binkey));

// 開始建立解密引擎,準備進行 DES-CBC 解密
var algId = info.EncryptionAlgorithm;
var cipher = (BufferedBlockCipher) PbeUtilities.CreateEngine(algId.ObjectID);
var param = PbeUtilities.GenerateCipherParameters(algId.ObjectID, password, algId.Parameters);
cipher.Init(false, param);

// 取出加密資料,進行解密
byte[] data = info.GetEncryptedData();
byte[] outBytes = new byte[cipher.GetOutputSize(data.Length)];
int len = cipher.ProcessBytes(data, 0, data.Length, outBytes, 0);

try {
len += cipher.DoFinal(outBytes, len);
} catch(Exception e) {
throw new Exception("failed DoFinal - exception " + e.ToString());
}

// outBytes 為解密完的 PrivateKeyInfo (PKCS#8),接著取出 PrivateKey
var p = PrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(outBytes));
var rsa = new RsaPrivateKeyStructure((Asn1Sequence) p.PrivateKey);
// privSpec 可用於解密
var privSpec = new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent);


手工實作法的步驟很繁雜,有沒有更好的方法呢?

想想可能還有其他已經包好的方法用了 PbeUtilities 來讀取加密私鑰了,就查查看。果不其然又讓我發現了 PrivateKeyInfoFactory.cs 原始碼,原來只要幾行:
using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;

const String pemp8header = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const String pemp8footer = "-----END ENCRYPTED PRIVATE KEY-----";
char[] password = "xxxxxx".ToCharArray(); // 解密密碼

// 取出檔案中 BASE64 編碼的部分,還原
StringBuilder sb = new StringBuilder(File.ReadAllText(@"B:\tmp.key"));
sb.Replace(pemp8header, "");
sb.Replace(pemp8footer, "");

String pubstr = sb.ToString().Trim();
byte[] binkey = Convert.FromBase64String(pubstr);
var info = EncryptedPrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(binkey));

// 重點方法: PrivateKeyInfoFactory.CreatePrivateKeyInfo() 一行抵十行
var p = PrivateKeyInfoFactory.CreatePrivateKeyInfo(password, info);
var rsa = new RsaPrivateKeyStructure((Asn1Sequence) p.PrivateKey);
// privSpec 可用於解密
var privSpec = new RsaKeyParameters(true, rsa.Modulus, rsa.PrivateExponent);


總結:開放原始碼是有必要的,畢竟最後還是得看原始碼找答案的嘛 (大誤)

2009/08/02

PKI Research #4 - Digital Signature

這篇預定要來研究數位簽章。

數位簽章的方式跟加解密剛好是反過來的,加解密使用公鑰加密,使用私鑰解密;數位簽章卻是使用私鑰簽章,使用公鑰驗證。

加解密的對象是明文檔案內容,而數位簽章的對象則是明文檔案內容經過雜湊後的值。常用的有 MD5 跟 SHA-1,聽說這兩個都有被碰撞破解的個案,所以使用 SHA256 (SHA-2 家族)。

.NET Fraamework 的 System.Security.Cryptography 命名空間實作不少相關雜湊,我直接寫成以下方法:
public static byte[] useSHA256(byte[] data){
SHA256 sha = new SHA256Managed();
return sha.ComputeHash(data);
}

流程圖一張:
http://en.wikipedia.org/wiki/File:Digital_Signature_diagram.svg

先從簽章實作。第一件事就是將文章進行 Hash,我使用上面寫好的 SHA256 方法來製造。然後要將此 Hash 以私鑰加密,因為 X509Certificate2 不能直接支援 RSA Private Key PEM 檔 (範例中使用 DER 格式方便 RFID Tag 儲存),所以我使用 Bouncy Castle C# 來進行私鑰的讀取,並用來加密 Hash 產生數位簽章 (稱為 Hash')。

另一邊進行驗證。驗證也需要將文章進行 Hash,以便跟數位簽章比對。一樣使用 SHA256 產生 Hash。然後將數位簽章使用 .crt 憑證內存的公鑰解密 (得到 Hash'),結果拿來跟 Hash 比對。如果完全正確 (Hash' = Hash) 表示文章沒有經過竄改,而且也能確定簽章者正是本人。但是 X509Certificate2 不能直接拿公鑰進行解密 (Decrypt),而是提供另一個驗證 (VerifyHash) 方法供呼叫,我也搞不懂為什麼不行。

public static byte[] signature(byte[] data){
byte[] hash = useSHA256(data);
try{
var seq = (Asn1Sequence) Asn1Object.FromStream(File.OpenRead(@"B:\OpenSSL\1-der.key"));
var rsa = new RsaPrivateKeyStructure(seq);
var privSpec = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2,
rsa.Coefficient
);

var encryptEngine = new Pkcs1Encoding(new RsaEngine());
encryptEngine.Init(true, privSpec);
return encryptEngine.ProcessBlock(hash, 0, hash.Length); // Digital Signature
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}


public static bool verify(byte[] message, byte[] sig){
byte[] hash = useSHA256(message);

var crt = new X509Certificate2(@"B:\OpenSSL\1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

return rsa.VerifyHash(hash, "SHA256", sig);
}

PKI Research #3 - RSA 加解密

CA 部分還沒搞定,但生成憑證簽署方面都可以利用 OpenSSL 來完成。OpenSSL 是開放原始碼專案,在 Unix-like 界非常廣泛,Windows 有別人 Port 好的版本:OpenSSL Binary Distributions,可以下載來玩看看。下指令的方式上一篇已經有許多篇參考資料,不再說明。

複習一下 .key 檔跟 .crt 檔的樣子:

user.key: (PEM-encoded)
-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/Qibv
B6ST5F056HCk0xvzm5rfO5A+q+4ncBMGMhkCAwEAAQJABGaLoICHrRjy2MX4ppm7
RWz/xLXxK0c+mJotbYVepQf1KYB6kPc4df1t4KrFFgxMyXzdJ/eiegf4j3HFT4Hs
gQIhAPiVVZNSMqp1uUx7OXB3GGW+3uslvA3+NvuAKqVguIo9AiEA7Hf6ykGeuRBN
k2EN+J+Vof4ryzFOtJGH+eyycmKPsQ0CIFJYteZ9nkcVhHKvh1GYQj7CQfpHn8pK
4k/iHz51kexJAiBwKs1kiVHv+QLDSQNmjtRcngNKBB6QWoQEkjlnNsdwNQIgbJYa
Ft4zOc8TnbXnGxXRfGVG0RZfXmxcQNv2yX2G4Vc=
-----END RSA PRIVATE KEY-----


user.crt: (PEM-encoded)
-----BEGIN CERTIFICATE-----
MIIBtDCCAV4CCQChpB3R8j5vwDANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQGEwJU
VzEPMA0GA1UECBMGVGFpd2FuMQ8wDQYDVQQHEwZUYWl3YW4xHDAaBgNVBAoTE05D
SFUgTUlTIERlcGFydG1lbnQxEjAQBgNVBAMTCXNvdWx0YWtlcjAeFw0wOTA3MzEw
NDE1MDJaFw0xMDA3MzEwNDE1MDJaMGExCzAJBgNVBAYTAlRXMQ8wDQYDVQQIEwZU
YWl3YW4xDzANBgNVBAcTBlRhaXdhbjEcMBoGA1UEChMTTkNIVSBNSVMgRGVwYXJ0
bWVudDESMBAGA1UEAxMJc291bHRha2VyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJB
AOWeK9TSYVnPEkmK45TeI7NrC8MzvwabSVg1aEuTmcNkLRg/QibvB6ST5F056HCk
0xvzm5rfO5A+q+4ncBMGMhkCAwEAATANBgkqhkiG9w0BAQUFAANBAGQhB8VKtY2N
hecmukSUmJkTzFWwT15fPmTGzf0KdrccNCSK2bE/u6bTW9dkUF12MkTaHNVytmKd
u7MoPuNvCnA=
-----END CERTIFICATE-----


所以,當我有了 .key 檔 (RSA 私鑰) 跟 .crt 憑證 (含公鑰的憑證) 後,該怎麼使用?這是本篇要研究的目標。

C# 利用 X.509 憑證加密,網路上有很棒的教學:使用X.509数字证书加密解密实务(二)-- 使用RSA证书加密敏感数据,圖文並茂很容易了解。基本上 .NET Framework 的 X509Certificate2 類別非常好用,它屬於 System.Security.Cryptography.X509Certificates 命名空間。只要建構元放入 .crt 憑證檔路徑,就可以抽出公鑰,加密資料。

解密的話,X509Certificate2 支援公私鑰綁一起的 PKCS#12,但不支援 OpenSSL 直接匯出的私鑰格式 (稱為 PEM 格式),當你直接指定 .key 檔位置會丟出例外,所以解密要找其他方案。

剛好無意間找到利用 Bouncy Castle for C# API 直接讀取 PEM 格式 RSA 私鑰進行解密的教學:用openSSL生成了一对RSA密钥,在C#里面怎么使用它啊? ,回答的第二項就是我們要的。你可能覺得奇怪它說第三項也可以用啊,但實際上那是讀取 .pfx 或 .p12 的 PKCS#12 個人憑證,直接讀取 OpenSSL RSA 私鑰 PEM 格式檔是絕對不行的。

DER (Distinguished Encoding Rules):
二進位內容,是屬於 ASN.1 制定的編碼之一。

PEM (Privacy-enhanced Electronic Mail):
就是 DER 格式經過 BASE64 編碼後所能見到的內容,通常會有 ----- BEGIN XXX ----- / ----- END XXX ----- 之類的東西包夾起來。由於 PEM 格式採用了 BASE64 編碼,文字都被編成常用的英文數字符號,方便於網路上傳送及複製 (如即時通訊、電子郵件等)。OpenSSL 預設產生的檔案都是 PEM 格式。當然也可以透過 -inform / -outform 來指定 DER 等格式。

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;


namespace PKI_RSAEnDecryptDemo {
class Program {
static void Main(string[] args) {
byte[] plainTextByte = Encoding.UTF8.GetBytes("超爽的,撿到100塊咧~咧?");

byte[] encTextType = encrypt(plainTextByte);
string base64 = Convert.ToBase64String(encTextType);
Console.WriteLine(base64);

byte[] oristr = decrypt_DER(encTextType);
Console.WriteLine(Encoding.UTF8.GetString(oristr));

Console.ReadKey();
}

public static byte[] encrypt(byte[] plainTextByte) {
var crt = new X509Certificate2(@"B:\OpenSSL\1.crt"); // Read X509 Certificate file
var rsa = (RSACryptoServiceProvider) crt.PublicKey.Key; // Get public key

byte[] Cryptograph = rsa.Encrypt(plainTextByte, false); // Encrypt
return Cryptograph;
}

public static byte[] decrypt(byte[] encTextType) {
AsymmetricCipherKeyPair keyPair;
using(var reader = File.OpenText(@"B:\OpenSSL\1.key")) { // Direct read pem format RSA private key file
keyPair = (AsymmetricCipherKeyPair) new PemReader(reader).ReadObject();
}
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, keyPair.Private);

return decryptEngine.ProcessBlock(encTextType, 0, encTextType.Length); // Decrypt
}

public static byte[] decrypt_DER(byte[] encText) {
try{
var seq = (Asn1Sequence) Asn1Object.FromStream(File.OpenRead(@"B:\OpenSSL\1-der.key"));
var rsa = new RsaPrivateKeyStructure(seq);
var privSpec = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2,
rsa.Coefficient
);

var decryptEngine = new Pkcs1Encoding(new RsaEngine());
decryptEngine.Init(false, privSpec);
return decryptEngine.ProcessBlock(encText, 0, encText.Length); // Decrypt
}catch(Exception e){
throw new IOException("problem creating private key: " + e.ToString());
}
}
}
}


---
參考資料:
OpenSSL 常用語法整理

2009/07/13

部署 VMware ESXi Server 4 on IBM System x3550

最後我選了 ESXi 作為虛擬化的方案。

第一:OpenVZ 基於一個 Linux,這個 OS 必須去維護它,我維護其他 OS 都嫌麻煩了;ESXi 是特化的 Linux 核心,幾乎不需維護 (定期注意系統更新即可)。

第二:OpenVZ 只能支援 Linux OS,因為它是 OS-Based 虛擬化的緣故;ESXi 使用 VMware 長久以來全虛擬化的結晶實作了一個半虛擬化的 Hypervisor 平台,可以運作多樣的 OS,當然也包含 Windows。

第三:這有點偏見,ESXi 安裝很簡單,OpenVZ 卻還要慢慢設定。

說了這麼多,最後我終於啟動了 System x3550,有如渦輪引擎的六具 6000 轉抽風扇瞬間轉起,那吵度真不是蓋的。卻發現它沒有 PS/2 插孔而只有 USB,因為手中沒有 USB 鍵盤所以只好又跑一趟電子街買一個 KIMYO NT$195 的 USB 陽春鍵盤。插上去後仔細看螢幕說明,按下 F1 按鍵進入 BIOS 設定,可以看到裝著兩顆 Intel Xeon E5405 2.0GHz、2GB 的記憶體及兩顆 SAS 167GB 硬碟。接著依指示按下 Ctrl + A 進入 RAID 管理。

我想 RAID 0 應該沒有勇者要試吧,這是伺服器耶。於是就選了 RAID 1 鏡像備份模式。建立完之後跳出設定,放入預先燒好的 ESXi 安裝光碟,開始安裝。

安裝過程沒什麼好說的,就是看指示按下對應按鈕,可能是 F8、F11 或 Enter 一直變換,大概是不想讓你一路跳過去吧。安裝過程根本沒難度,只是 ESXi 4 的安裝條件相當嚴苛,通常不能安裝都是卡在 RAID 卡或是網路卡不支援,這台 IBM System x3550 算是通過考驗了。安裝完成重開機,就進入 ESXi 介面。

介面什麼都沒有。總之先按下 F2 設定 root 密碼跟伺服器 IP 吧,成功輸入 IP、Subnet mask、Gateway和 DNS 後,就完成了網路設定。最後打開被封印的 SSH 遠端管理功能,這是一定要的 (參考文章)。過程中比較難的大概是 vi 指令,因為許久未碰都忘光了XD 按 a 進編輯模式,刪除註解的 # 字元,按下 Esc 跳出編輯模式,輸入 :wq (冒號: Shift + ;) 寫入並離開就大功告成,最後重開一下。

要管理必須透過 vSphere Client 連線 (可以 HTTP 連到伺服器 IP 有下載連結),而且只有 Windows 版,怪。安裝完成後發現 Windows 7 RC 版不能登入,顯示 clients.xml 錯誤啥的。官方也有網友回報討論串,官方解答竟然是叫你用 7 的 XP Mode 執行,這真是我聽過最棒的回答法了,去你的。還好下面有強者提出解套法:去其他安裝 .NET 3.5 SP 的電腦上找 System.dll 複製,修改 VpxClient.exe.config 文件,使用 BAT 下環境變數執行,真的成功,真是太棒了。

最後就利用 GUI 登入,上傳作業系統 ISO 檔供虛擬系統安裝時使用,設定虛擬系統跟 VMware 其他產品非常像,這是最易上手的地方。最後啟動虛擬系統,按照安裝 OS 的步驟來安裝 (放入光碟、更改 BIOS 開機順序),就大功告成。記得最後裝個 VMware Tools 來使系統更快更穩。

灌了一個 CentOS 5.3 版,跑看看再說。在 CentOS 的 eth0 設定使用手動 IP 設定,也可以取得固定 IP,有如另一台伺服器在線運作一般,真好玩。也可以 SSH 連進去管理。

2009/07/10

PKI

PKI(Public Key Infrastructure;公開金鑰基礎建設)

何謂PKI?
提供在電腦網路運作的個人資料、商務與商業能夠如同信封簽章與封印般的技術。
密碼學上,藉著憑證管理中心(CA)將使用者的個人身分跟公開金鑰鏈結在一起。

PKI所能達到的目標?
機密性、真確性、驗證性、不可否認性

PKI概念介紹(PDF)

資通PKI應用競賽 歡迎來PK!

ARES uPKI套件是免費提供的 (在這個比賽),但是它好像是用在 SmartCard 上,RFID上不知可不可行。

2009/07/08

C# Delegate 委派 使用方法

之前講到委派 (Delegate),當時不清楚做法。查了一些資料後,大概了解它是一個代理者的角色,代替你執行你要執行的方法。例如說你可以手動打開電視開關來收看電視,也可以利用電視遙控器 (代理者) 來幫你執行打開電視的工作。

C# 提供 delegate 修飾子來宣告一個委派方法:

public delegate String getNameDelegate(String n);

委派方法的參數個數、順序、型態乃至於回傳型態都必須跟要被委派的方法一樣 (但變數名稱可以不相同)。下面是我們要委派這個 getNameDelegate 方法呼叫的一個方法:

public static String getName(String name){
return "g1: " + name;
}


所以我們把 getName 想成打開電視機,getNameDelegate 當作遙控器,這樣可以多少理解吧?

要呼叫委派方法,有許多方法,隨著 C# 版本的演進已經越來越方便,就從最複雜的開始說起吧。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
class Program {
public delegate String getNameDelegate(String n);

static void Main(string[] args) {
getNameDelegate g0 = new getNameDelegate(getName);
getNameDelegate g1 = getName;
getNameDelegate g2 = delegate(String n) { return "g2: " + n; };
getNameDelegate g3 = n => "g3: " + n;

Console.WriteLine(g0("0"));
Console.WriteLine(g1("1"));
Console.WriteLine(g2("2"));
Console.WriteLine(g3("3"));
Console.ReadKey();
}

public static String getName(String name){
return "g1: " + name;
}
}
}


首先是 getNameDelegate g0 = new getNameDelegate(getName); 這段,這是最早的方法。你就將 getNameDelegate 給實體化,並在其建構元上指定要代為執行的方法 (如 getName) 就可以了。

因為這樣太麻煩,所以後來的 C# 支援這種寫法:getNameDelegate g1 = getName; 是不是更直覺呢?

C# 2.0 中,增加了匿名方法的建立,像我們的 getName 這麼簡單的一行,可以簡化為
getNameDelegate g2 = delegate(String n) { return "g2: " + n; };
這樣,利用 delegate(){ } 來定義匿名方法。

C# 3.0 中,更以 Lambda 運算式取代匿名方法,一行 getNameDelegate g3 = n => "g3: " + n; 輕鬆打發。 n => "g3: " + n; 就是所謂的 Lambda 運算式。例如 x => x * x 就是「傳入 x,執行 x * x 並回傳」。

最後準備好 g1 等委派物件後,就可以開始執行了。執行的方式有兩種,一種是 g1.Invoke(); 來引發,另一種是更為直覺的 g1(); 直接執行。


這麼看起來委派只是代為執行,並沒有什麼好用的感覺啊。像上面的例子,我大可直接呼叫 getName() 就好,為什麼還要繞一圈以 getNameDelegate 包裝起來再呼叫呢?脫褲子放屁嗎?

getNameDelegate g1 = getName;
g1("Scribe");

getName("Scribe");

They're the same!

雖然我現在還搞不懂委派有什麼好的,不過我發現委派他可以一次呼叫代為多個方法,這樣就比較有用多了。例如我按下遙控器的開關,就可以幫我把電視、冷氣、電燈等開關一次打開,不是很方便嗎?所以下面要教你怎麼一次委派多個方法。

using System;

namespace ConsoleApplication1 {
class Program {
public delegate void consoleDelegate(String txt);
public delegate void printNameDelegate(String n, consoleDelegate p);

static void Main(string[] args) {
var g0 = new printNameDelegate(printName);
g0 += delegate(String n, consoleDelegate p) { p("Call from anonymous func: " + n); };
g0 += (n, p) => p("Call from Lambda: " + n);

g0("scribe", print2Console);
Console.ReadKey();
}

public static void printName(String name, consoleDelegate p) {
p(name);
}

public static void print2Console(String txt){
Console.WriteLine(txt);
}
}
}

Result:
scribe
Call from anonymous func: scribe
Call from Lambda: scribe


var g0 = new printNameDelegate(printName);
g0 += delegate(String n, consoleDelegate p) { p("Call from anonymous func: " + n); };
g0 += (n, p) => p("Call from Lambda: " + n);

上面三行分別以不同的方式創造出委派物件,跟上面範例的不同點是以 += 串接,讓 g0 一次代理多個方法,最後再一次執行。所以 g0() 的結果有三行。這樣就可以呼叫一個委派物件,同時作數件事情了。

值得一提的是本範例用了兩個 delegate,其中一個用在 print2Console 上,讓其他方法能夠叫用。printName 第二個參數就是 delegate,其實很像是方法的指標之類的。(因為不可以直接指定第二個變數型別為 print2Console,所以使用委派方式)


我比較喜歡 delegate{} 建立匿名方法跟直接使用 () 來呼叫委派物件,你呢?

參考資料:C# 筆記:重訪委派-從 C# 1.0 到 2.0 到 3.0

2009/07/07

在 WPF Application 中使用 Timer

一般來說在 Windows Forms 程式可以使用工具箱的 Timer (System.Windows.Forms) 來進行,另外也有更為精確的 System.Timers.Timer 可以用。但是在 Windows Presentation Foundation 應用程式中,沒有辦法使用 System.Windows.Forms.Timer,而 System.Timers.Timer 雖然仍可以使用,但是在時間觸發的事件中如果你去修改 UI 控制項的文字等等,會警告你只有擁有 UI 控制項的執行緒可以修改屬性。要解決這個方法可以用 Delegate 委派方法,但是這是 C# 的新玩意,Java沒有這種東西,所以不太懂。另一個方法是設定 Timer 的 SynchronizingObject 屬性,讓這個獨立的執行緒跟 UI 執行緒屬於同一個,但是 WPF 程式的頂層 Window 沒有實作 ISynchronizeInvoke 介面的樣子,所以無法使用。

System.Windows.Forms.Timer:
Windows Forms 程式用
計時執行緒跟 UI 執行緒是同一個 (或者說根本只有一個執行緒)
可能造成主畫面沒有反應的狀況 (因為相同執行緒的關係)

System.Timers.Timer:
所有 .NET 程式皆可使用
計時執行緒跟 UI 執行緒是不同個 (因此有執行緒同步問題)
不會造成主畫面沒有反應的情況


於是拜 Google 大神,得到了 System.Windows.Threading.DispatcherTimer 這個物件,可以用在 WPF 程式上,而且它所產生的執行緒跟擁有 UI 的執行緒是同一個,就不必擔心無法修改的問題。

private void btnTimer_Click(object sender, RoutedEventArgs e) {
// 設定 Timer 以自動讀取
DispatcherTimer t = new DispatcherTimer();
t.Tick += new EventHandler(t_Elapsed);
t.Interval = new TimeSpan(1000000);
t.Start();
}

private void t_Elapsed(object sender, EventArgs e) {
// ... do something...
}


值得注意的是 TimeSpan 建構元接受的數字是 100 奈秒單位,上面的 1000000 為 100 毫秒的意思。

System.Windows.Threading.DispatcherTimer:
所有 .NET 程式皆可使用
計時執行緒位於 Dispatcher 佇列之中,可以存取 UI 控制項
不會造成主畫面沒有反應的情況 (未測試,可能是因為統一由 Dispatcher 管理)

2009/07/03

.NET 字串轉 Byte 、Byte 轉字串解法

因為 RFID 的 Block 讀寫是採用低階的 byte,一般使用不太可能直接使用 byte 而會是 String 一類的東西,是故我們必須學會如何把字串轉成 byte,反過來 byte 轉成 String 的方法。

"張京介" → Write to Block X
"張京介" → Convert to byte[] → {177, 105, 168, 202, 164, 182} → Write to Block O


.NET Framework 的 System.Text.Encoding 類別庫提供一系列的字碼頁轉換方式,其中有 Default (環境預設編碼,例如正體中文便是 Big5)、UTF7、UTF8、Unicode (UTF-16)、UTF32、ASCII 等編碼。要把字串變成 byte,則可以使用 GetBytes() 方法,以下是例子:

using System.Text;

String name = "張京介";
Encoding.UTF8.GetBytes(name); // {229, 188, 181, 228, 186, 172, 228, 187, 139}
Encoding.Default.GetBytes(name); // {177, 105, 168, 202, 164, 182}
Encoding.Unicode.GetBytes(name); // {53, 95, 172, 78, 203, 78}


要從 byte 轉回來,也有 GetString() 方法可以用,細節就不再說明。

2009/07/02

MySQL Prepare() 使用法 + UTF-8 亂碼解決

MySqlCommand 提供的 Prepare() 可以讓你避免自行組合字串的不便性和 SQL Injection 的防止。前面提到過 21.2.4.6 節有範例可以參考。

MySqlConnection conn = new MySqlConnection("server=localhost;user=root;database=test;port=3306;charset=utf8;");
MySqlCommand cmd = new MySqlCommand("INSERT INTO test VALUES (@ID, @Name)", conn);
cmd.Prepare();
cmd.Parameters.Add("@ID", MySqlDbType.Int16);
cmd.Parameters.Add("@Name", MySqlDbType.String);

for(int i = 6; i < 10; i++){
cmd.Parameters["@ID"].Value = i;
cmd.Parameters["@Name"].Value = "修羅パンツ" + i.ToString();
cmd.ExecuteNonQuery();
}


只要下好 SQL Command 後呼叫 Prepare(),再塞入 Parameters,最後 Execute 就可以完成了。這邊範例是先定義好 Parameters 的型態 (ID 是整數、Name 是字串)後,利用迴圈批次新增資料 (Parameters["@ID"].Value) 再呼叫 ExecuteNonQuery 新增,就可以達成重複使用。

另外,如果在 Connection String 時沒有設定 charset=utf8 (注意是 utf8 不是 utf-8!),寫入時會預設使用 iso-8859-1 (Latin1) 來寫入,非英數字就會變成 ???。另外新增 MySQL 資料表時,Table 跟每個欄位也要設定使用 utf8 (utf8_general_ci) 這樣讀寫才會正常。

總結,要排除亂碼,在 MySQL 資料表要設定為 utf8 外,程式建立連線時也要明顯定義 charset=utf8 才可以。

MySQL Connector/Net 使用法

1. 安裝 MySQL Connector/Net (參考文章)
2. 開啟 Visual C# 2008,將 MySql.Data 加入物件瀏覽器的自訂元件集(編輯自訂元件集),在.NET分頁可以找到 MySql.Data。



3. 在專案要先「參考」MySql.Data才能在專案中使用,快速新增參考的方式很簡單,在 2. 項中我們已經新增了 MySql.Data,只要選取後按下圖中的按鈕就可以快速加入參考。



4. 以下是連接 MySQL 資料庫的範例程式碼:

using System;
using System.Windows.Forms;
using MySql.Data.MySqlClient;


namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
MySqlConnection conn = new MySqlConnection("server=localhost;user=root;database=test;port=3306;charset=utf8;");
try {
conn.Open();
MySqlCommand cmd = new MySqlCommand("SELECT * FROM test", conn);
MySqlDataReader rdr = cmd.ExecuteReader();

while(rdr.Read()) {
this.label1.Text += rdr[0] + " -- " + rdr[1] + "\n";
}

rdr.Close();
conn.Close();
} catch(Exception ex) {
MessageBox.Show(ex.ToString());
}
}
}
}


Form1 有個 Label 叫做 label1,作為顯示資料庫的項目用。