PowerBuilder 軟件加密實驗

Pb讀硬盤序列號和加密rsa算法

很實用的代碼,環境:PB9.0通過 測試 :PB10.5-win8 64位下無法獲取硬盤序列號,該加解密算法存在漏洞只能兩位兩位操作,十位是0就無法正常解密)
說明:
大家知道,每當我們格式化軟盤或硬盤時系統都會給它分配一個序列號,即用DOS命令dir顯示出的"Volume Serial Number is 0A41-0E0A"。該序
列號是隨機產生的,且具有唯一性。也就是因爲這個原因許多軟件的測試版本利用該項技術使測試版一旦過了限定期限就不能再使用,即使將該軟件
重新安裝也無濟於事。另外,有些共享軟件的註冊碼也是通過這個序列號來生成的。同樣的方法我們也可以運用到軟件的防拷貝技術。那麼如何才能
實現上述功能呢?

  爲了達到目的我們必須首先能夠得到硬盤的序列號,其次,爲了安全起見我們最好再選擇一種加密算法,將加密後的硬盤的序列號作爲密文公開
存放,軟件通過解密得到明文,即硬盤的序列號,通過將解密後的硬盤序列號和實際的硬盤序列號相比較得出程序是否合法。這一步當然是由應用程
序祕密運行,用戶根本不知道,從而達到軟件的二次加密目的,同時也隱藏了軟件的合法性識別過程,使破譯者無從下手。下面就來談談如何具體實
現。

一、如何讀取硬盤序列號

  要讀取硬盤序列號我們可以用匯編來實現,但畢竟不容易,況且也不能有效的結合到PB腳本中。在PB中我們可以通過調用Windows提供的外部函數
GetVolumeInformationA()來實現。這相對來說比較簡單。

  該函數的原型爲:

  BOOL GetVolumeInformation(

  LPCTSTR lpRootPathName,

  LPTSTR lpVolumeNameBuffer,

  DWORD nVolumeNameSize,

  LPDWORD lpVolumeSerialNumber,

  LPDWORD lpMaximumComponentLength,

  LPDWORD lpFileSystemFlags,

  LPTSTR lpFileSystemNameBuffer,

  DWORD nFileSystemNameSize

  上述原型中,參數類型只要是以"LP-"開頭的表明該參數用的是長指針(Long Pointer)類型,即在PB中調用時的參數傳遞是通過引用傳遞。在8
個參數中對我們真正有用的只有兩個LPCTSTR lpRootPathName和LPDWORD lpVolumeSerialNumber。其中參數lpRootPathName是指向文件系統根目錄的
地址,我們需要用它來指明所要獲取序列號的硬盤盤符;參數lpVolumeSerialNumber是返回的硬盤序列號的地址,這正是我們需要的。

  衆所周知,PB在調用任何外部函數前都要首先進行函數聲明,可以將聲明放在全局或局部函數聲明中。具體聲明如下:

  Function Boolean GetVolumeInformationA( &

  ref String ls_Rootpath, &

  ref String ls_volumnename, &

  Ulong lul_VolumeNameSize, ref Ulong lul_VolumeSerialNumber, &

  ref Ulong lul_MaximumComponentLength, &

  ref Ulong lul_FileSystemFlags, &

  ref String ls_FileSystemNameBuffer, &

  Ulong lul_FileSystemNameSize &

  ) Library "Kernel32.dll"

 

  上述聲明中,"ref"指明是該參數是通過引用傳遞的,有關函數引用的詳細內容請參見有關教程。聲明完畢我們不能馬上進行調用,還必需確保已
爲它分配足夠的內存空間,即使是參數引用傳遞也是這樣,否則的話將會出現調用錯誤,這跟C語言的引用調用不同,這一點往往被忽視,希望讀者能
夠注意。也就是爲什麼我在調用該函數前將有些字符串參數給它預先分配了多達256個字符空間以及給一些整型類型的參數賦初始值256。完整的讀取
硬盤序列號的程序代碼如下:

 

String ls_Rootpath, ls_volumnename

ls_Rootpath = "C:" // 指定要得到序列號的硬盤,

// 一般情況都是C盤,除非你能保證用戶存在其它邏輯盤或物理盤

ls_volumnename = Space(256) // 分配足夠的空間,下同

Ulong lul_VolumeNameSize

lul_VolumeNameSize = 256

Ulong lul_VolumeSerialNumber, lul_MaximumComponentLength, lul_FileSystemFlags

lul_MaximumComponentLength = 256

String ls_FileSystemNameBuffer

ls_FileSystemNameBuffer = space(256)

Ulong lul_FileSystemNameSize

lul_FileSystemNameSize = 256

beep(1)

boolean lb_rtn

lb_rtn = False

lb_rtn = GetVolumeInformationA(ls_Rootpath, ls_volumnename, lul_VolumeNameSize,

lul_VolumeSerialNumber, lul_MaximumComponentLength, lul_FileSystemFlags,

ls_FileSystemNameBuffer, lul_FileSystemNameSize)

if lb_rtn = true then

MessageBox("提示","函數調用成功!")

else

MessageBox("提示","函數調用失敗!")

end if

sle_1.text = String(lul_VolumeSerialNumber) // 得到硬盤序列號

 

  一旦讀取成功我們的任務也就完成了近一半,接下來要做的是怎樣選擇一個合適的加密算法。

二、選取一個優秀的加密算法

  1、數據加密概述

  早在幾千年前人類就已經有了通信保密的思想和方法。但直到1949年,信息論創始人香農發表著名文章,論證了一般經典加密方法得到的密文幾
乎都是可破譯的。密碼學才得以進入了一個新的發展時期。70年代後期,美國的數據加密標準DES和公開密鑰密碼體制的出現成爲近代密碼學發展史上
的兩個重要里程碑。

  公開密鑰密碼體制的概念是由Difie與Hellman於1976年提出。所謂公開密鑰密碼體制就是加密密鑰與解密密鑰不同,是一種由已知加密密鑰推導
出解密密鑰在計算上是不可行的密碼體制。其中,基於數論中大數分解問題的RSA體制曾被ISO/TC97的數據加密技術委員會SC20推薦爲公開密鑰數據加
密標準。

  2、RSA體制的基本原理

  該體制是根據尋求兩個大素數比較簡單,而將它們的乘積分解開則極其困難這一原理來設計的。在已提出的公開密鑰算法中它是最容易理解和實
現的。RSA在世界上許多地方已成事實上的標準。ISO幾乎(但沒有明確)已指定RSA用作數字簽名標準。該算法已經經受住了多年深入的密碼分析,雖
然密碼分析者既不能證明也不能否定RSA的安全性,但這恰恰說明了該算法有一定的可信度。它的安全性是與大數分解密切相關的。我想通過下表你將
會對它的安全性有一個較好的認識,它給出了在計算機每一微妙做一次操作的假定下分解不同大小的N所需要的時間。

  N的十進位數  50    75    100   200

  時間      3.9小時  104天  74年  3.8X1015年

  RSA加密算法具體如下:

  (1)選取兩個大素數,p和q。爲了獲得最大程序的安全性,兩個素數的長度一樣。並計算乘積N(N=pq)。

  (2) 隨後計算出N的歐拉函數ф(N)=(p-1)(q-1),ф(N)定義爲不超過N並與N互素的數的個數。

  (3)從[0,ф(N)- 1]中隨機選取加密密鑰e,使得e和ф(N)互爲素數。   

  (4)計算出滿足公式ed=1 modф(N)的d,d爲解密密鑰。   

  (5)若用整數X表示明文,整數Y表示密文(X,Y均小於N),則加解密運算爲:

  加密:Y = Xe mod N   

  解密:X = Yd mod N   

  注意,其中的d和N也互素。e和N是公開密鑰,d是祕密密鑰。兩個素數p和q應捨棄,但千萬不要泄密哦。

  3、相關數學背景知識

  (1)素數:素數是一個比1大,其因子只有1和它本身,沒有其它數可以整除它的數。素數是無限的。例如,2,3,5,7……等。

  (2)兩個數互爲素數:指的是它們除了1之外沒有共同的因子。也可以說這兩個數的最大公因子是1。例如,4和9,13和27等。

  (3)模變換:兩個數相模,如A模N運算,它給出了A的餘數,餘數是從0到N-1的某個整數,這種運算稱爲模運算。

  4、算法的具體實現

  從RSA的基本原理我們得知,對明文進行加密選擇一個合適的e很重要,如果你選擇合適的話,RSA的加密速度將快得多,並且也不會因爲用戶機器
的限制而要做更多的變換(指在計算中爲了避免數據的溢出所進行的轉換,畢竟我們用的是PC機再說也用不着很高的安全性)。最常用的三個e值是
3,17,65537。在這裏我們取的e等於3,當然到底選取哪個e值並沒有規定,這裏只是爲了演示方便罷了。

  根據算法定義,

  (1)爲了方便起見我們選取素數p = 3和q = 11,則N = pq = 3 * 11 = 33。   

  (2)ф(N)=(p-1)(q-1)= 2 * 10 = 20。   

  (3)從[0,ф(N) - 1]中,即,[0, 19]之間任意選取加密密鑰e = 3,且e和ф(N)互素。

  (4)如何從公式ed=1 modф(N)求出解密密鑰d?

  由模的定理我們可以將公式ed=1 modф(N)轉換成形式ed= k * ф(N)+ 1,即3d = k * 20 + 1,將0,1,2,3…依次代入k,求出d。取k =
1,得d = 7。

  讀者可以通過編程實現隨機選取p和q來求出相應的N,e,d。

  (5)進行加解密。

  對明文進行加密

  根據定義,我們首先要根據N的值對明文進行分組,每個分組的值應小於N。如果要加密固定的消息分組,那麼可以在它的左邊填充一些0(零)並
確保該值比N小。例如,我們要對數據X=172035594進行加密(在我的計算機上C盤的序列號是0A41-0E0A,轉換成十進制就是172035594),我們首先要
將它分成小於N(N=33)的若干小組。可以分成,X1=17,X2=20,X3=3,X4=5,X5=5,X6=9,X7=4。對第一分組X1運用加密公式得到加密密文Y1=X1e
mod N = 173 mod 33 = 29,依次將其餘分組進行加密得到,Y2=14,Y3=27,Y4=26,Y5=26,Y6=3,Y7=31。即密文Y= 2914272626331。我們可以將密
文存儲在文件或註冊表中,每當應用程序啓動時先讀取密文,並將其解密,再將解密後的結果與硬盤序列號進行比較,以此來判斷軟件是否合法。在
實際運用中我們可以隨時通過程序修改密文,比如,將密文去掉一位或將密文顛倒等,就可以實現諸如測試版軟件的使用限制問題,

  對密文進行解密

  對密文進行解密同樣要首先對密文進行分組,使每個分組都小於N。將密文Y=2914272626331分組成:

  Y1=29,Y2=14,Y3=27,Y4=26,Y5=26,Y6=3,Y7=31

  這時我們一定要注意,不要急於將將各分組代入解密公式X=Yd mod N,如果這樣做了我們所得到的明文將是X=1202811913,並不是加密時的明
文!是不是加密算法有錯?絕對不是。回顧加解密的公式,我們不難發現它們做的都是先將一個數進行n次方運算然後在做模運算。問題就出在"n次方
運算"上,千萬不要忽略PowerBuilder中數值的取值範圍,在其它的編程語言中也是如此。在本例中我給明文和密文用的都是unsigned long類型,它
的32位所允許最大值是4294967295,的確很大,但我們不能保證一個數在進行了7次方後不超過該最大值。其實,這種情況在對明文加密時也是會發生
的,只是33的3次方是35937,遠小於最大值,我們將其忽略罷了。

  好在問題並不像我們想象的那麼複雜。由模的運算規律得知,模運算像普通的運算一樣,它是可交換的、可結合的、可分配的。而且,簡化運算
每一箇中間結果的模n運算,其作用與先進行全部運算,然後再簡化模n運算是一樣的。比如,

  (A * B) mod N = ((A mod N) * (B mod N)) mod N。

  因此,

  X = Y7 mod N

  = (Y3 * Y4)mod N

  = ((Y3 mod N)*(Y4 mod N))mod N

  當然,我們也可以將Y7分解成更多項的乘積。將分組後的密文Y1至Y7依次代入上式得出密文爲X = 17 20 3 5 5 9 4。即爲正確明文,解密成功。

  在實際的運用中考慮到PB沒有現成的乘方運算函數,爲了便於讀者理解原程序是如何實現RSA加密算法的本文所採用的方法是通過FOR…NEXT語句
循環來實現乘方運算,讀者可以將其做成一個函數,在使用的時候調用。RSA加解密算法的完整程序代碼如下:

 

// 以下參數由RSA加密算法得來

integer li_e, li_d, li_n

li_e = 3 // 設置指數e,加密密鑰

li_d = 7 // 設置指數d,解密密鑰

li_n = 33 // 設置N:兩個素數得乘積

string ls_str

ls_str = Trim(sle_1.text) // 將明文轉換成字符串,以便隨後進行分組

ulong lul_temp

lul_temp = 0

ulong lul_x, lul_y // lul_x: 加密明文; lul_y: 加密密文

int I

do until ls_str = ""

lul_temp = Integer(left(ls_str, 2))

if lul_temp >= li_n then // 將明文分組,且每組均小於N(N=33)

lul_temp = Integer(left(ls_str, 1))

ls_str = right(ls_str, len(ls_str)-1)

else

ls_str = right(ls_str, len(ls_str)-2)

end if

lul_y = 1

for I = 1 to li_e // 進行乘方運算

lul_y = lul_y * lul_temp

next

lul_y = mod( lul_y, 33) // 根據加密公式計算密文

sle_2.text = trim(sle_2.text) + string(lul_y) // sle_2.tex中存放的是加密後的密文

loop

 

 

ls_str = Trim(sle_2.text) // 與加密同理,將密文轉換成字符串,以便隨後進行分組

ulong lul_x0, lul_x1

do until ls_str = ""

lul_temp = Integer(left(ls_str, 2))

if lul_temp >= li_n then // 將密文分組,且每組均小於N(N=33)

lul_temp = Integer(left(ls_str, 1))

ls_str = right(ls_str, len(ls_str)-1)

else

ls_str = right(ls_str, len(ls_str)-2)

end if

// 由於考慮到乘方運算得結果可能會超出數值所允許得最大取值,

// 因此對解密公式進行適當轉換,lul_x = lul_x0 * lul_x1

lul_x0 = 1

lul_x1 = 1

// 假如解密密鑰是7,則先進行數的4次方運算取模,在進行數的3次方運算取模

for I = 1 to 4

lul_x0 = lul_x0 * lul_temp

next

lul_x0 = mod( lul_x0, 33)

for I = 1 to li_d - 4

lul_x1 = lul_x1 * lul_temp

next

lul_x1 = mod( lul_x1, 33)

lul_x = mod(lul_x0 * lul_x1, 33) // 根據解密公式計算明文

sle_3.text = trim(sle_3.text) + string(lul_x) // sle_3.tex中存放的是解密後的明文

loop

 

  總結,文中所提供的程序代碼已經在PowerBuilder 9.0,Windows 98環境下運行通過。本文所討論的通過讀取硬盤序列號方法來實現程序加密技
術同樣可以運用到其它程序語言中,只是具體的實現方法略有差異罷了。由於本人水平有限,錯誤和不足之處再所難免,因此還敬請同行們給予指
教。


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/qafifa/archive/2009/10/10/4651443.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章