2013年8月18日 星期日

Python M2Crypto - RSA 的 Encrypt, Decrypt, Sign and Verify

        這裡要介紹一些RSA基本知識,以及如何產生一把 RSA 的非對稱式(asymmetric) 公開金鑰 (public key)和 私有金鑰(private key)。並且用一個簡單的範例程式來解釋如何用RSA來進行加密解密和簽章驗證的應用。

RSA的基本知識和重要名詞

        RSA是一種asymmetric演算法,它是透過一組public key和private key來進行Encrypt/Decrypt或是Sign/Verify。它的優點是:資料交換過程當中,雙方只需要拿到對方的public key即可加密資料,然後將加密後的密文(cipher)傳送給對方。只有正確的接收者才會有private key能夠解出密文(cipher)。缺點是,加密或是解密的效能較差不適合用於大量的資料加密。RSA的private key的建議長度最少是1024 bits以上,才具有基本的安全性, private key長度越長所需要的加解密運算成本越高,相對也會更加安全。

Key Length

        RSA 的Key Length 決定了RSA被破解的強度,長度越長基本上越難以被破解。但是,不是永遠不能破解,只是目前還沒有提出有效的破解方法。

Public Exponent

        公開指數是為了滿足RSA演算法所需要的一個整數,而且必須是質數Exponent ,然而很多密碼學的函式庫所內建的RSA Public Exponent都是65537的質數。有個討論在探討RSA Public Exponent 選擇 3 是否不夠安全的問題,有興趣的可以參考這網頁。無論如何,基本上選擇 65537的質數應該是一個較為安全且建議的作法。雖然選擇過大的質數會影響解密和驗證的效率,但是除非是要應用在運算能力非常弱的環境上,否則選擇小的質數當作Public Exponent應該不建議的,特別是Padding Mode很差的情形。但是,若選用合適的Padding Mode時,即使選擇3當作 RSA Public Exponent對於安全性並沒有太大的不同。

Padding Mode

        RSA基本上必須透過隨機的Padding方式,以確保即使每次都加密相同明文(Plan-Text)時候,不會產生完全相同的密文(Cipher-Text)。而OpenSSL支援 4 種 Padding Modes,以下截取自OpenSSL官方文件。而M2Crypto目前只支援 RSA_PKCS1_PADDING 和 RSA_PKCS1_OAEP_PADDING,根據官方文件上的建議就是使用 RSA_PKCS1_OAEP_PADDING。
RSA_PKCS1_PADDING
RSA_PKCS1_OAEP_PADDING
RSA_SSLV23_PADDING
RSA_NO_PADDING

RSA Encrypt / Decrypt 的基本範例程式解說

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import M2Crypto
import M2Crypto.BN as BN

def generate_keypair_as_pem(key_len, exponent):
    def empty_callback():
        pass

    rsa = M2Crypto.RSA.gen_key(key_len, exponent, empty_callback)
    # Get RSA Public Key in PEM format
    buf = M2Crypto.BIO.MemoryBuffer('')
    rsa.save_pub_key_bio(buf)
    public_key = buf.getvalue()

    # Get Private Key in PEM format
    buf = M2Crypto.BIO.MemoryBuffer('')
    rsa.save_key_bio(buf, None)
    private_key = buf.getvalue() # RSA Private Key
    
    return (public_key, private_key)

if __name__ == '__main__':
    keylen = 1024         # 1024 bits
    exponent = 65537  
    padding = M2Crypto.RSA.pkcs1_oeap_padding
    
    # Generate RSA key-pair in PEM files for public key and private key 
    public_key, private_key = generate_keypair_as_pem(keylen, exponent)
    message = 'This is a plain text data'
        
    # Use public key to encrypt 'message'
    buf = M2Crypto.BIO.MemoryBuffer('')
    buf.write(public_key)
    rsa1 = M2Crypto.RSA.load_pub_key_bio(buf)
    cipher_message = rsa1.public_encrypt(message, padding)

    # Use private key to decrypt 'cipher_message'
    rsa2 = M2Crypto.RSA.load_key_string(private_key)
    plaintext_message = rsa2.private_decrypt(cipher_message, padding)

  1. 使用RSA演算法之前,必須產生 RSA 金鑰(Public Key and Private Key Pair)
  2. 第 22 和 23 行指定 key長度為 1024 bits,選用的public exponent 為 65537 
  3. 產生 RSA Key-Pair 在第 8 行,透過 M2Crypto.RSA.gen_key 函數並指定 key 長度和 public exponent。第 3 個參數基本上只要給 empty_callback 即可,若是沒有給時,你呼叫這個函數會在 standard output 中出現類似....++的符號。它只是用來表示初始化  RSA key-pair 的進度狀態。此時我們可以取得 RSA 的 instance,存在 rsa 變數中。
  4. 分別取得 RSA public key 和 private key。M2Crypto 提供的方法,會將 Public key 和Private key 轉成 PEM 格式
  5. 第 10 ~12 行是取得 RSA public key 的方式,第 15 ~17 行是取得RSA private key的方式。差別在於 rsa.save_key_bio(buf, None) 的第 2 個參數設定為 None的原因,是希望直接取得真正的 RSA private key 而不要再經由 aes_128_cbc的方式來保護。否則執行到這段程式碼時,系統會在 console 要求使用者輸入 passphrase 。細節請參M2Crypto官方文件。 但是,一般來說如果是要存在系統中某個地方時候,是會透過另一種加密演算法來保護這把 RSA private key。
  6. 第 31 ~33 行是載入 RSA public key 取得 RSA的 instance 存在變數 rsa1 中,第 34 行呼叫  rsa1.public_encrypt(message, padding)來加密明文(Plain Text),其中指定 padding mode 為 OEAP Padding 。
  7. 第 37 ~38 透過 RSA private key 解開密文(Cipher Text),也必須指定相同的 padding mode 。


RSA Sign and Verify Signature 的基本範例程式解說

以下這個範例,是 sender 以安全的方式傳送一個 message 給 receiver 的 RSA 常見的應用。為了方便解釋,以下我們稱 A 為 sender, B為 receiver 。它大致上有 7 個步驟如下:

  1. A與B各自產生一個 RSA的 key-pair (private and public key)
  2. A與B交換自己的 public key。
  3. A使用B的 public key來加密訊息產生 Cipher message。
  4. A再使用自己的 private key 來為 Cipher message 產生 Signature。
  5. 然後 A 把 Signature 和 Cipher message 傳送給 B。
  6. B 必須用 A的 public key 來驗證所收到的 Signature 是否正確。
  7. B 再用自己的 private key 來解開 Cipher Message,即可得到A傳送的訊息內容。 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import M2Crypto
import M2Crypto.BN as BN

def generate_keypair_as_pem(key_len, exponent):
    def empty_callback():
        pass

    rsa = M2Crypto.RSA.gen_key(key_len, exponent, empty_callback)
    # Get RSA Public Key in PEM format
    buf = M2Crypto.BIO.MemoryBuffer('')
    rsa.save_pub_key_bio(buf)
    public_key = buf.getvalue()

    # Get Private Key in PEM format
    buf = M2Crypto.BIO.MemoryBuffer('')
    rsa.save_key_bio(buf, None)
    private_key = buf.getvalue() # RSA Private Key
    
    return (public_key, private_key)

def get_data_digest(data):
    msg_digest = M2Crypto.EVP.MessageDigest('sha256')
    msg_digest.update (data)
    digest =  msg_digest.digest()
    return digest

def generate_secure_msg(A_private_key, B_public_key, message):
    padding = M2Crypto.RSA.pkcs1_oaep_padding
    buf = M2Crypto.BIO.MemoryBuffer('')
    buf.write(B_public_key)
    rsa1 = M2Crypto.RSA.load_pub_key_bio(buf)
    cipher_message = rsa1.public_encrypt(message, padding)
    # Use A's private key to sign the 'cipher_message'
    digest1 = get_data_digest(cipher_message)
    rsa2 = M2Crypto.RSA.load_key_string(A_private_key)
    signature = rsa2.sign(digest1, 'sha256')
    return cipher_message, signature

def read_secure_msg(A_public_key, B_private_key, cipher_message, signature):
    try:
        # Use A's public key to verify 'signature'
        buf = M2Crypto.BIO.MemoryBuffer('')
        buf.write(A_public_key)
        rsa3 = M2Crypto.RSA.load_pub_key_bio(buf)                
        # Verify
        digest2 = get_data_digest(cipher_message)
        rsa3.verify(digest2, signature, 'sha256')
        # Use B's private key to decrypt 'cipher_message'
        rsa4 = M2Crypto.RSA.load_key_string(B_private_key)        
        padding = M2Crypto.RSA.pkcs1_oaep_padding
        plaintext_message = rsa4.private_decrypt(cipher_message, padding)
        return plaintext_message
    except Exception as err:        
        print 'Verify Fail:%r'% err
        raise 

if __name__ == '__main__':
    keylen = 1024         # 1024 bits
    exponent = 65537
    padding = M2Crypto.RSA.pkcs1_oaep_padding
    
    # Generate RSA key-pair in PEM files for public key and private key 
    A_pub_key, A_priv_key = generate_keypair_as_pem(keylen, exponent)
    
    # Generate RSA key-pair in PEM files for public key and private key 
    B_pub_key, B_priv_key = generate_keypair_as_pem(keylen, exponent)

    # A is sender, B is receiver
    msg = 'A want to send this message to B'

    # Sender's behavior
    cipher_msg, signature = generate_secure_msg(A_priv_key, B_pub_key, msg)

    # Receiver's behavior
    plain_text = read_secure_msg(A_pub_key, B_priv_key, cipher_msg, signature)

附註:
        實務上 RSA 通常都會搭配 AES 做 secure key 的保護,純粹是根據你所要保護的資料大小以及應用所著重的特點而有不同,此外 RSA 並不適合用來加密大量資料。

Reference:

Python M2Crypto - AES 的 Encrypt 與 Decrypt

        這邊要介紹有關 AES 的 Encrypt 和 Decrypt ,其中會介紹一些 AES 的基本知識和相關名詞以及一個簡單的範例程式說明。

AES的基本知識

        AES(Advanced Encryption Standard)是一種對稱式(symmetric)的加密演算法,是透過對每個固定大小的4x4位元矩陣區塊(block = 128 bits = 16 bytes),對其每一個元素進行多次交互置換和XOR運算。簡單來說,就是用同一把 secret key 進行Encrypt和Decrypt的動作。優點是對於資料大的檔案加解密的速度較快,而且容易透過硬體實作,運算所需要的記憶體較少,但是缺點是在資料交換的過程中,雙方必須取得這把相同的secret key。否則,接收到加密檔案的接收者,無法解開加密後的檔案。而它的安全性強度取決於金鑰(secret key)的長度,目前美國國防安全局審核認定的AES標準演算法secret key長度有128 bits, 192 bits和256 bits 三種。secret key的長度越長就越安全,但是相對的加密所需的時間就越多,然而實際上secret key長度對於加密時間的影響並不大,主要還是取決於所選擇的 Block Cipher Mode以及需要加密的資料大小。

AES演算法中常見的專有名詞

        在AES演算法中,不管是網路上的文章或是API文件中,常見到以下這些名詞:

Initialization vector (IV)

        "初始化向量"或稱"起始變數",它的用途主要在於避免相同的資料加密多次都產生相同的密文(Cipher Text)。因此,使用上必須要注意的是,相同的一把金鑰(secret key)在加密的時候,不可以使用相同的 IV,否則就破壞了AES的安全性。此外 IV 本身並不需要保護,它是可以被公開的。而IV的最大長度必須是 16 bytes,而且產生IV的方式必須是無法預測的,也就是隨機產生即可。

以下是關於幾種常見的IV類型:
(1) Fixed IV
  顧名思義就是,在使用同一把金鑰(secret key)在加密的時候,所使用的IV都是固定的。這會造成兩個相同的 plain-text 的區塊,產生的 cipher-text 區塊是相同的。

(2) Counter IV
  是指在使用同一把金鑰(secret key)在加密的時候,每次加密一個訊息時,都將 IV 累加 1。

(3) Random IV
  是指隨機產生一個亂數當作 IV。建議的作法是將第一個 IV 當作密文的第一個 cipher-text 區塊。從第二個 cipher-text 區塊才是真正的加密資料。

(4) Nonce-Generated IV
  是指使用相同的一把金鑰(secret key)在加密的時候,用一個 nonce 來產生 IV 。nonce 是指 number used once 的意思,主要的精神在於同一把金鑰(secret key)不可以使用相同的 nonce。例如:假設選擇 nonce = 0 開始後,使用相同的一把金鑰(secret key)在加密的時候,不可以出現使用 nonce = 0 第 2 次。

Padding

        由於AES加密過程,是針對每個固定大小的區塊(16 bytes),進行多次的交互置換和XOR運算,因此當需要被加密的資料小於矩陣區塊16 bytes 的時候,或是資料的size 不是 16 bytes的倍數時,為了讓加密能夠順利進行,必須將資料的 size 補齊到能夠被 16 bytes 整除的大小。舉例來說,假設需要保護的資料總長度只有 5 bytes,那在進行AES加密之前,必須補齊資料長度達到16 bytes。至於,不同補齊的方式可以參考 Use Padding in Encryption 這篇文章。

特別註記,以下的圖出自於維基百科 (https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)

Block cipher mode 

  • ECB (Electronic codebook,ECB)
    ECB是對於每一個資料區塊都用同一把金鑰(secret key)去加密,而且沒有使用 IV。缺點是在於相同的資料區塊,加密後的密文(Cipher Text)會是相同的。優點是每個區塊都可以獨立進行加密,因此可同時對每個區塊進行加密。

  • CBC (Cipher-block chaining)
    CBC是一種串鏈的加密方式,第一個資料區塊必須加入IV和金鑰(secret key)進行加密,之後將加密後的密文(Cipher Text)作為第二個資料區塊的IV再加上金鑰進行加密,以此類推下去,直到所有區塊都被加密完成。這種方式在加密過程當中,下一個區塊必須依賴這個區塊加密後的結果才能夠得到IV,因此無法同時進行。但是在解密的時候,可以同時對所有區塊進行解密,因為前後兩個密文(Cipher Text)區塊,後面的密文區塊要解密時候所需要的IV就是前一個密文區塊。

  • CFB (Cipher feedback)
    CFB 則是將 IV 和金鑰(secret key) 產生出 密文(Cipher Text)區塊,然後把密文(Cipher Text)區塊和明文資料區塊進行XOR運算後的值,當做下一個資料區塊的IV再和金鑰(secret key)產生出下一個密文區塊,以此類推下去做相同的運算,直到所有區塊都被加密完成。

  • OFB (Output feedback)
    OFB 則是將 IV 和 金鑰 (secret key) 產生密文區塊(Cipher Block),然後將Cipher Block和明文區塊 PlainText 進行 XOR 運算。而這個區塊產生的 Cipher Block 則當作下一個資料區塊加密處理所需的 IV。


  • CTR (Counter)
    CTR 則是先透過所謂的 nonce (其實就是IV) 加上可以在長時間內每次都產生不重復 sequence 整數的 counter 所組合出來的一個整數值, 接著用同一把金鑰 (secret key) 對這個整數值加密後的產生一個密文區塊 (Cipher Block), 最後把這個密文區塊和明文區塊(PlainText)進行XOR運算。這種做法每個區塊都可以獨立地進行加密或是解密,因此可運用在平行處理的加解密運算。

AES Encrypt / Decrypt 基礎範例程式解說


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import M2Crypto.EVP as EVP
import cStringIO

ENCRYPT_OP = 1
DECRYPT_OP = 0

def aes_encrypt(iv, secret_key, plain_text):
    cipher = EVP.Cipher(alg='aes_128_cbc', 
                        key=secret_key, 
                        iv=iv,
                        op=ENCRYPT_OP)

    input_buffer  = cStringIO.StringIO(plain_text)        
    cipher_data1 = cipher.update(input_buffer.read())
    cipher_data2 = cipher.final()
    input_buffer.close()
        
    output_buffer = cStringIO.StringIO()
    output_buffer.write(cipher_data1)
    output_buffer.write(cipher_data2)
    cipher_text = output_buffer.getvalue()    
    output_buffer.close()
    
    return cipher_text
    
def aes_decrypt(iv, secret_key, cipher_text):
    cipher = EVP.Cipher(alg='aes_128_cbc', 
                        key=secret_key, 
                        iv=iv, 
                        op=DECRYPT_OP)
    
    input_buffer = cStringIO.StringIO(cipher_text)
    plain_text1 = cipher.update(input_buffer.read())
    plain_text2 = cipher.final()
    input_buffer.close()
    
    output_buffer = cStringIO.StringIO()
    output_buffer.write(plain_text1)
    output_buffer.write(plain_text2)
    plain_text = output_buffer.getvalue()    
    output_buffer.close()
    
    return plain_text

if __name__ == '__main__':

    IV = 'c782dc4c098c66cb' # 16 bytes
    secrect_key = 'c286696d887c9aa0' # 16 bytes
    
    data = 'This is a 48-byte message (exactly 3 AES blocks)'

    cipher_text = aes_encrypt(IV, secrect_key, data)
    plain_text = aes_decrypt(IV, secrect_key, cipher_text)

    print('Cipher Text length is %d bytes'% len(cipher_text))    
    print('Cipher Text is as following')
    print('####################')
    print(cipher_text)    
    print('####################')            
    print('Plain Text=%s'% plain_text)

        EVP是OpenSSL針對對稱式加密所提供的模組,它針對某些對稱式(Symmetric)密碼學演算法在處理加密或是解密時,所需要的API規格。

  1. 因此在加密或是解密之前,需要產生一個Cipher的物件來負責加密或是解密。
  2. 初始化Cipher物件時,第 8 和 27 行程式的 alg 參數指定使用 128-bits 長度的 AES-CBC模式的演算法。這個 alg 參數在AES演算法的使用上,可以指定以下幾種分別代表不同長度和模式:
    • ECB
      • aes_128_ecb 
      • aes_192_ecb
      • aes_256_ecb
    • CBC
      • aes_128_cbc
      • aes_192_cbc
      • aes_256_cbc
    • CFB
      • aes_128_cfb
      • aes_192_cfb
      • aes_256_cfb
    • OFB
      • aes_128_ofb
      • aes_192_ofb
      • aes_256_ofb
  3. 初始化Cipher物件時,第 9 和 28 行程式的 key 參數是用來指定加密或是解密時,所需要用到的金鑰(secret key)。它的長度至少需要128 bits 也就是16 bytes。若你是使用aes_256_cbc模式,則金鑰長度必須為256 bits 等於 32 bytes長度。
  4. 初始化Cipher物件時,第 10 和 29 行程式的 iv 參數是用來指定初始向量(Initialization vector),它的最大長度為 16 bytes。
  5. 初始化Cipher物件時,根據要做加密或是解密來決定 op的參數。若是用於加密,則 op 參數要設定為 1 。反之,用於解密時,則 op 參數必須指定為 0。
  6. 在程式碼的的 第 47 行,我們選定的 IV 長度為 16 bytes 。根據我的實驗結果,在M2Crypto API 使用上,即使所給的 IV不足 16 bytes,它還是可以執行。在 secret key不變的情況下,每次執行後的密文都會改變。但是,如果指定的 IV 剛好是 16 bytes長度時,執行多次後的密文是相同的。如果指定的 IV 長度大於  16 bytes,則超過16 bytes後的資料將不會被當做 IV 使用。然而,演算法上 IV 的長度應該要取決於你所使用的 AES mode 與金鑰長度來決定。
  7. 在程式碼的第 48 行,我們選定的 secret key 長度為 16 bytes 。因為這個範例程式指定的 AES模式為 aes_128_cbc,就 AES 128 bits 加密所需要的長度就是16 bytes 。但是,在 M2Crypto API 使用上,它的行為和 IV 有類似的狀況。以這個範例來說,如果 secret key 少於 16 bytes 而 IV 不變的情況下,對相同的資料加密後所產生的密文,對相同的資料執行多次後的密文都會不同。但是,如果 secret key 剛好指定 16 bytes 時候,對相同資料執行多次加密後所得到的密文是相同的。如果指定的 secret key 超過 16 bytes 時候,超過16 bytes 後的資料不會被當作 secret key來使用。

Important warring: You should select the correct cipher mode,  for example: CCM or GCM mode. (Update 2022/03/04) The reason is the CBC mode is vulnerable to padding oracle attacks.

Reference:

Python GUI in Tkinter

過去也曾透過Java Swing進行GUI Programming,2008年工作時上需要透過Python來開發GUI。從沒寫過Python 到開始寫用Tkinter來開發GUI程式,大概花了幾天。 目前,撰寫plus-in讓Python能夠透過去連Serial Console, 另外還把一些C的Header檔案和陣列的宣告和使用進行簡易的 Parsing和Link並resolve出一些Constants的值以及Configuration資訊。 接著根據這些資訊透過Python自動產生GUI。

說實在的,我用的很不習慣。特別是它的Layout只有三種管理的方式Pack、Grid、Place三種,讓我覺得Twinter不是非常直覺。我是覺得用它來寫一些簡單方便的GUI還可以,如果要拿來寫複雜的視窗程式 可能會花很多時間。否則就要另外在包一層這樣用起來會比較順手。

Tkinter主要的事件反應都是透過configure每個Widget所對應的處理函數。以Button來說,透過指定command對應的函數,當Button發生press事件時會呼叫對應的函數。

以下這張圖是Demo1.py執行的結果,這個範例中SHOW_JF1、SHOW_JF2 與SHOW_JF3三個Button分別透過三種寫法來處理Button的事件反映。



Demo1.py的程式碼如下所示:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
from Tkinter import *

class ActionListener:
    main = None
    def __init__(self, main):
        self.main = main;

def action(self):
        print("ActionListener!");
        self.main.showJF2();

class Main:

    def __init__(self):   
        tk = Tk();
        self.mjf = Frame(tk, borderwidth = 3, relief=SUNKEN);   
        self.jf1 = Frame(self.mjf);
        self.jf1.configure(borderwidth = 3, background = "green", relief=GROOVE);

        bt1 = Button(self.jf1, text="JF1");
        bt2 = Button(self.jf1, text="JF1");
        bt3 = Button(self.jf1, text="JF1");
        bt1.pack(side = LEFT, fill=BOTH, expand=YES);
        bt2.pack(side = LEFT, fill=BOTH, expand=YES);
        bt3.pack(side = LEFT, fill=BOTH, expand=YES);


        self.jf2 = Frame(self.mjf);
        self.jf2.configure(borderwidth = 3, background = "red", relief=RIDGE);

        bt1 = Button(self.jf2, text="JF2");
        bt2 = Button(self.jf2, text="JF2");
        bt3 = Button(self.jf2, text="JF2");
        bt1.pack(side = LEFT, fill=BOTH, expand=YES);
        bt2.pack(side = LEFT, fill=BOTH, expand=YES);
        bt3.pack(side = LEFT, fill=BOTH, expand=YES);        



        self.jf3 = Frame(self.mjf);
        self.jf3.configure(borderwidth = 3, background = "yellow", relief=RIDGE);
        bt1 = Button(self.jf3, text="JF3");
        bt2 = Button(self.jf3, text="JF3");
        bt3 = Button(self.jf3, text="JF3");
        bt1.pack(side = LEFT, fill=BOTH, expand=YES);
        bt2.pack(side = LEFT, fill=BOTH, expand=YES);
        bt3.pack(side = LEFT, fill=BOTH, expand=YES);

        btFrame = Frame(tk);
        bt1 = Button( btFrame, text="SHOW_JF1");
        bt2 = Button( btFrame, text="SHOW_JF2");
        bt3 = Button( btFrame, text="SHOW_JF3");
        
        #[ 1. Anonymous Callback Function with arguments
        bt1.configure(command = lambda s=self, event="JF1": s.showJF(event));

        #[ 2. ActionListener with arguments
        bt2.configure(command = ActionListener(self).action);

        #[ 3. Direct Callback Function
        bt3.configure(command = self.showJF3);

        bt1.pack(side = LEFT, fill=BOTH, expand=YES);
        bt2.pack(side = LEFT, fill=BOTH, expand=YES);
        bt3.pack(side = LEFT, fill=BOTH, expand=YES);

        btFrame.pack(side = TOP)
        self.mjf.pack(side = TOP, fill=BOTH, expand=YES);

        newBtFrame = Frame(tk);
        newBtFrame.pack(side = TOP ,fill=X, expand=YES);
        bt1 = Button( newBtFrame, text="Bottom Button1");
        bt2 = Button( newBtFrame, text="Bottom Button2");
        bt1.pack(side=LEFT);
        bt2.pack(side=RIGHT);

        self.showJF("JF2");

    def showJF(self, event):
        print("Anonymous Callback Function with arguments");
        if(self.currJF != None):
            self.currJF.pack_forget();

        if (event=="JF1"):
            self.jf1.pack(side = TOP, fill=BOTH, expand=YES);
            self.currJF = self.jf1;

        if (event=="JF2"):
            self.jf2.pack(side = TOP, fill=BOTH, expand=YES);
            self.currJF = self.jf2;

        if (event=="JF3"):
            self.jf3.pack(side = TOP, fill=BOTH, expand=YES);
            self.currJF = self.jf3;
           
    def showJF2(self):
        if(self.currJF != None):
            self.currJF.pack_forget();

        self.jf2.pack(side = TOP, fill=BOTH, expand=YES);
        self.currJF = self.jf2;

    def showJF3(self):
        if(self.currJF != None):
            self.currJF.pack_forget();

        self.jf3.pack(side = TOP, fill=BOTH, expand=YES);
        self.currJF = self.jf3;

def main():
    m = Main();
    mainloop();

Reference:

http://www.pythonware.com/library/an-introduction-to-tkinter.htm
http://effbot.org/tkinterbook/
http://www-acc.kek.jp/WWW-ACC-exp/KEKB/control/Activity/Python/TkIntro/introduction/index.htm


Python GUI: Tcl/Tk

2008年夏天在 MOXA-IW 開發公司內部使用的自動化測試工具,花了30個工作天,寫了約五千行Python也算是完 成第一版的雛形。 基本上此系統架構,部分GUI畫面是透過組態檔設定即 可自動產生,並 支援資料輸入的防呆機制。另外透過我所設計的Assert Model可將所需判斷的參數都自動產生GUI提供測試人員方便輸入。在系統 執行測試時,可透過XML組態檔方式來描述Assert的邏輯以及參數擷取方式。

此套工具可 指定排程,透過Console操控多台公司的產品,進行測試的設備組態設定, 並自動更新Firmware。隨後執行指定的Tool Actions,並針對 Assert的判定結果產生報表。每個TestCase都會有一個對應的log檔案,詳細記錄每個 TestCase執行的細節。

以下是部分的工具畫面,最後是報表畫面。











報表畫面(log牽涉指令細節,連結已被移除)

Struct in Python and Heritance

Python is an extendable programming language , as you know it supports Object-Oriented programming though it is not a strong type language. How do you write a class to support heritance if you want to use it as 'struct' type in C Programming ? I think this is an interesting question. I will demo an example that how I achieve it.

First, you need to study 'struct — Interpret strings as packed binary data' in Python manuals. It will tell you how to pack and unpack binary data.

Second, you have to study '3.4.1 Basic customization' to know behavior of function '__new__'.


  1 import string;
  2 import struct;
  3 
  4 class StructMeta(type):
  5     __TYPE__ = ['x','c','b','B','?','h','H','i','I',
  6                 'l','L','q','Q','f','d','s','p','P'];
  7     ___struct___ = '___struct___';
  8     ___fields___ = '___fields___';
  9     ___fmt___ = '___fmt___';
 10 
 11     def __new__(cls, clsname, clsbases, clsdict):
 12         """
 13         This function will be recursive invoked when classes be loaded. 
 14         """
 15         clsdict2 = dict();
 16         fields = [];
 17         format = [];
 18         if(len(clsbases)>0):
 19             # It will come here if only if cls has parent class!
 20             parentFields = clsbases[0].__getattribute__(clsbases[0], StructMeta.___fields___);
 21             for field in parentFields :
 22                 fields.append(field);
 23                 
 24             parentFormat = clsbases[0].__getattribute__(clsbases[0], StructMeta.___fmt___);
 25             format.append(parentFormat);
 26 
 27         for field in clsdict:
 28             structDef = clsdict[field];
 29             if(string.find(field, StructMeta.___struct___)  < 0): 
 30                 clsdict2[field] = structDef;              
 31                 continue;
 32             for fieldDef in structDef:
 33                 name = fieldDef[0];
 34                 fmt = fieldDef[1];
 35                 if(fmt not in StructMeta.__TYPE__):
 36                     raise Exception("Not support type '"+fmt+"' at " + name);                     
 37                 format.append(fmt);
 38                 defaultVal = fieldDef[2];
 39                 if(name in fields):
 40                     raise Exception("Redefinition '"+name+"' at "+ field); 
 41                 fields.append(name);
 42                 clsdict2[name] = defaultVal;
 43  
 44         clsdict2[StructMeta.___fmt___] =  string.join(format,'');
 45         clsdict2[StructMeta.___fields___] = fields;
 46         return type.__new__(cls, clsname, clsbases, clsdict2);
 47         
 48 
 49 class StructObject:
 50     __metaclass__ = StructMeta;
 51     ___endian___ = '!';
 52     #@ native native 
 53     #= native standard 
 54     #< little-endian standard 
 55     #> big-endian standard 
 56     #! network (= big-endian) standard 
 57     """
 58     It support define struct type 
 59     """
 60     def __init__(self):
 61         pass;
 62     
 63     def setEndian(self, edian):
 64         self.___endian___ = edian;
 65     
 66     def unpack(self, buf):        
 67         values = struct.unpack(self.___endian___+ self.___fmt___,  buf);
 68         i = 0;
 69         for field in self.___fields___:
 70             self.__setattr__(field, values[i]); 
 71             i += 1;        
 72 
 73 class A(StructObject):    
 74     ___struct___A = (
 75         ('field1', 'H', 0),
 76     );        
 77 
 78 class B(A):
 79     ___struct___B = (
 80         ('field2', 'I', 0),
 81     );
 82 
 83 class C(B):
 84     ___struct___C = (
 85         ('field3', 'H', 0),
 86     );
 87     
 88 if __name__ == "__main__":
 89     
 90     obj = C();
 91     buf = '\x00\x02\x00\x00\x00\x04\x00\x06';
 92     obj.unpack(buf);
 93     print(obj.field1);
 94     print(obj.field2);
 95     print(obj.field3);
 96 
 97 ############### Output ###################
 98 # 2                                      #
 99 # 4                                      #
100 # 6                                      #
101 ##########################################




As you see in output, class A, class B and class C have different fields then 'obj' is an instance of class C. Then we unpack binary data and we could access fields that defined in different class.The class C has fields which inherits from class B and class A.

What is that we need to be careful of this design ?
1. You need to maintain definition of fields by yourself.
2. Don't redefine same filed name. It doesn't support field hiden.
3. The '___struct___' will not be an actual field in an instance of class C.

2013年8月17日 星期六

Python M2Crypto 常見的應用範例程式

Introduction

        M2Crypto 是一個讓Python開發人員能夠在透過API呼叫的方式來使用OpenSSL的套件。簡單來說,使用M2Crypto重新包裝過後的API,讓開發人員寫Python程式時,不再需要使用command line方式呼叫openssl來處理Encrypt/Decrypt/Sign/Verify...等等的功能。然而,M2Crypto對於一般不熟悉密碼學的開發人員而言,使用上會有一點點的進入門檻,特別是對於OpenSSL本身也不熟的開發人員,當然如果你已經是一個資深的開發人員也瞭解密碼學,則應該不會有這方面的困擾。

        M2Crypto在國外的論壇上或是網站上,都可找到一些針對不同應用的範例程式。我本身覺得這些應用在專案中是很常見,所以本文章會稍作整理,也當作自己工作上的學習紀錄。以下,我將會將整理一些之前專案中需要有用到M2Crypto的應用,但是不會深入探討各種密碼學的演算法細節,若你需要詳細的演算法請到 wikipedia 研讀。

本文章會談到以下這些應用:
  1. AES 的 Encrypt 與 Decrypt
  2. RSA 的 Encrypt, Decrypt, Sign and Verify
  3. 產生 Certificate Sign Request 與 Sign Certificate  
  4. SMIME Encrypt/Decrypt/Sign/Verify
  5. 完整的 RSA 與 AES 搭配的範例
針對以上這些M2Crypt的基本應用,每篇文章都會有基本知識的介紹以及範例程式。