申明:本文旨在技術交流,并不針對“正方”(新版正方教務系統密碼處理方式也換了,只是用這個做個引子而已……)!本文并沒有什么深度,僅探討已知明文、密文和算法的情況下逆向得Key的可能!
經常遇到基友求助類似Base64編碼的解碼(先不說是不是Base64,下文會做說明)。如:p6FDpXlnQ1tHlLZK+NvA1hwfeND8NdXt1q6whqP6WODTEBP4UzzjnDQ== 這個很像Base64編碼吧,但你用標準Base64去解碼得到的結果明顯不是想要的(這個有N多可能)。
且從編碼、解碼說起吧,最簡單的編碼(為什么不叫加密、解密呢?看你用它干什么了……)是將字符映射,例如:
a=e
b=f
c=g
……
v=z
w=a
x=b
y=c
z=d
這只是個例子,還有數字、特殊字符等沒寫進去呢。當然你也可以用沒有規律的映射方式去編碼。這樣的編碼如果用于加密密碼的話會有什么缺陷呢?
假定某人已經擁有了幾組明文和對應的密文,那么他可以用手上的這幾組明文和密文尋找規律來還原映射關系,然后再由這個映射關系輕松破解其它密文。
由此,我們將可還原明文的加密算法分為如下幾類(為什么要密文可還原?還是讓廠商來回答吧!):
① 如:bcdef=fghij ? cdefg=ghijk…… 這樣的加密不需要工具,目測到規律然后還原。
② 略復雜一點的加密算法,密文找不到規律,可以防止用戶的破解。但是如果加密解密算法側漏了,那樣所有用戶的密碼都會慘遭破解!
③ 復雜的加密算法,使用自定義Key加密明文,也就是說密碼是明文和Key的某種運算之后生成的。這種算法有個優點,就算算法側漏,沒有Key也是無法解密的(下面開始討論)。類似:pass=encode(str,key); str=decode(pass,key) 。
④ 更復雜的算法……(待補充)
為什么是再議呢?因為正方教務系統的“加密”、“解密”運作方式早已被討論N多次了,網上甚至可以找到此算法的N多版本(可自行Baidu、Google)。 兩個典型的場景是:
① 有多組密文,已知幾組密文對應的明文和加密算法,但是由于無法獲得加密所使用的Key,無法解密其它密文。
② 由系統某缺陷可以獲得加密后的密文(當然明文是自己的密碼這個是可控的),獲得了別人的密文后由于無Key,無法解密。
為了研究此算法嘗試逆得Key,我決定再寫個VB版的。由于事先已從網上查知算法位于/bin/zjdx.dll中,所以找起來就簡單了,網上找得一份“源碼”使用Reflector載入zjdx.dll,由登錄頁Default2跟進到jiam方法如圖:
源碼如下(已寫上注釋):
public string jiam(string PlainStr, string key)
{
string text3;
int num3 = 1;
int num4 = Strings.Len(PlainStr);//取密碼明文長度
for (int num1 = 1; num1 <= num4; num1++)
{
string text6 = Strings.Mid(PlainStr, num1, 1);//從第一位開始每一次循環取密碼中的下一個字符
string text2 = Strings.Mid(key, num3, 1);
if (((((Strings.Asc(text6) ^ Strings.Asc(text2)) < 0x20) | ((Strings.Asc(text6) ^ Strings.Asc(text2)) > 0x7e)) | (Strings.Asc(text6) < 0)) | (Strings.Asc(text6) > 0xff))//將明文中截到的某位字符的ASSCII碼和Key中截到的某位字符的ASSCII碼進行異或運算,若結果是不可打印字符的ASSCII碼則臨時字串直接加上明文中截到的這個字符
{
text3 = text3 + text6;
}
else//若異或后的結果是可打印字符的ASSCII碼(33-126),則臨時字串加上ASSCII碼是異或結果的字符
{
text3 = text3 + StringType.FromChar(Strings.Chr(Strings.Asc(text6) ^ Strings.Asc(text2)));
}
if (num3 == Strings.Len(key))//假如Key中截到的字符用到最后一位了,則從頭開始使用Key
{
num3 = 0;
}
num3++;
}
if ((Strings.Len(text3) % 2) == 0)//假如最后的臨時結果字符長度是偶數個,那么最終結果=反轉字符的前半部分+反轉字符的后半部分
{
string text4 = Strings.StrReverse(Strings.Left(text3, (int) Math.Round(((double) Strings.Len(text3)) / 2)));
string text5 = Strings.StrReverse(Strings.Right(text3, (int) Math.Round(((double) Strings.Len(text3)) / 2)));
text3 = text4 + text5;
}
return text3;//返回最終結果
}
可見jiami函數中傳入了兩個參數,其中PlainStr為要加密的明文,key為加密使用的key。此函數邏輯如注釋。
關于異或運算請參見:http://technet.microsoft.com/zh-cn/subscriptions/csw1x2a6(v=vs.80).aspx
為了方便于理解下文,作如此解釋:三個數a,b,c 假如(a xor b=c)那么(a xor b xor a=b),(a xor b xor b=a)。
ASSCII碼對照表請參見:http://www.96yx.com/tool/ASC2.htm 33-126為可打印字符。
至此,已完全清晰了jiam的邏輯,將此函數移植于VB程序,代碼如下:
Public Function jiam(ByVal PlainStr As String, ByVal key As String) As String
Dim strChar, KeyChar, NewStr As String
Dim Pos As Integer
Dim i, intLen As Integer
Dim Side1, Side2 As String
Pos = 1
For i = 1 To Len(PlainStr)
strChar = Mid(PlainStr, i, 1)
KeyChar = Mid(key, Pos, 1)
If ((Asc(strChar) Xor Asc(KeyChar)) < 32) Or ((Asc(strChar) Xor Asc(KeyChar)) > 126) Or (Asc(strChar) < 0) Or (Asc(strChar) > 255) Then
NewStr = NewStr & strChar
Else
NewStr = NewStr & Chr(Asc(strChar) Xor Asc(KeyChar))
End If
If Pos = Len(key) Then Pos = 0
Pos = Pos + 1
Next
If Len(NewStr) Mod 2 = 0 Then
Side1 = StrReverse(Left(NewStr, CInt((Len(NewStr) / 2))))
Side2 = StrReverse(Right(NewStr, CInt((Len(NewStr) / 2))))
NewStr = Side1 & Side2
End If
jiam = NewStr
End Function
那么如果已知Key的情況下,要解密該如何書寫解密的代碼呢?
在zjdx.dll中反編譯得到的解密函數如下:
public string jiemi(string PlainStr, string key)
{
string text3;
int num2 = 1;
if ((Strings.Len(PlainStr) % 2) == 0)
{
string text4 = Strings.StrReverse(Strings.Left(PlainStr, (int) Math.Round(((double) Strings.Len(PlainStr)) / 2)));
string text5 = Strings.StrReverse(Strings.Right(PlainStr, (int) Math.Round(((double) Strings.Len(PlainStr)) / 2)));
PlainStr = text4 + text5;
}
int num3 = Strings.Len(PlainStr);
for (int num1 = 1; num1 <= num3; num1++)
{
string text6 = Strings.Mid(PlainStr, num1, 1);
string text2 = Strings.Mid(key, num2, 1);
if (((((Strings.Asc(text6) ^ Strings.Asc(text2)) < 0x20) | ((Strings.Asc(text6) ^ Strings.Asc(text2)) > 0x7e)) | (Strings.Asc(text6) < 0)) | (Strings.Asc(text6) > 0xff))
{
text3 = text3 + text6;
}
else
{
text3 = text3 + StringType.FromChar(Strings.Chr(Strings.Asc(text6) ^ Strings.Asc(text2)));
}
if (num2 == Strings.Len(key))
{
num2 = 0;
}
num2++;
}
return text3;
}
其實不需要這個函數,稍懂編程的人便可根據加密函數寫出對應的解密函數。
解密邏輯:
+--判斷密文長度是否是偶數|下一步
|是(重組密文) 否|下一步
|一位位截得密碼和key的某位異或|下一步
|根據異或結果組合字符|下一步
+最終結果|
根據此邏輯書寫的VB代碼如下:
Public Function jiemi(ByVal PlainStr As String, ByVal key As String) As String
Dim strChar, KeyChar, NewStr As String
Dim Pos As Integer
Dim i As Integer
Dim Side1 As String
Dim Side2 As String
Pos = 1
If Len(PlainStr) Mod 2 = 0 Then
Side1 = StrReverse(Left(PlainStr, CInt((Len(PlainStr) / 2)))) '反轉明文密碼最左邊一半的字符
Side2 = StrReverse(Right(PlainStr, CInt((Len(PlainStr) / 2)))) '反轉明文密碼最右邊一半的字符
PlainStr = Side1 & Side2
End If
For i = 1 To Len(PlainStr)
strChar = Mid(PlainStr, i, 1) '一個一個處理plainstr中的字符
KeyChar = Mid(key, Pos, 1) '循環使用key中的字符
If ((Asc(strChar) Xor Asc(KeyChar)) < 32) Or ((Asc(strChar) Xor Asc(KeyChar)) > 126) Or (Asc(strChar) < 0) Or (Asc(strChar) > 255) Then
NewStr = NewStr & strChar
Else
NewStr = NewStr & Chr(Asc(strChar) Xor Asc(KeyChar))
End If
If Pos = Len(key) Then Pos = 0
Pos = Pos + 1
Next
jiemi = NewStr
End Function
現在加密和解密的函數都已完全寫出來了,由此得到了兩個公式:jiam(明文,key)=密文 ? ?jiemi(密文,key)=明文
但是在上面的場景是已知明文和密文,有沒有可能運算得到Key呢:GetKey(明文,密文)=key ?
答案是肯定的,因為由jiam()函數得知:明文和密文的長度一定相等。由于已知異或運算的法則如此:明文(異或)循環key=密文、{明文(異或)key}(異或)明文=循環Key,所以密文(異或)明文=循環Key。
根據此邏輯書寫VB代碼如下:
Public Function GetKey(ByVal PlainStr As String, ByVal Pass As String) As String
Dim strChar, KeyChar, NewStr As String
Dim Pos As Integer
Dim i As Integer
Dim Side1 As String
Dim Side2 As String
Pos = 1
If Len(PlainStr) Mod 2 = 0 Then
Side1 = StrReverse(Left(PlainStr, CInt((Len(PlainStr) / 2)))) '反轉明文密碼最左邊一半的字符
Side2 = StrReverse(Right(PlainStr, CInt((Len(PlainStr) / 2)))) '反轉明文密碼最右邊一半的字符
PlainStr = Side1 & Side2
End If
For i = 1 To Len(PlainStr)
strChar = Mid(PlainStr, i, 1) '一個一個處理plainstr中的字符
KeyChar = Mid(Pass, Pos, 1) '循環使用pass中的字符
If strChar = KeyChar Then
NewStr = NewStr & "*"
Else
NewStr = NewStr & Chr(Asc(strChar) Xor Asc(KeyChar))
End If
Pos = Pos + 1
Next
GetKey = NewStr
End Function
到這里,我們遇到了一個問題,那就是密文中的有些字符并沒有經過異或運算而直接加入到了結果之中,也就是Key在某此字符處并沒有起作用,所以在這些地方無法逆出相應的Key字符。那該怎么辦呢?前面我們已經提到了,我們有幾組明文和密碼的對照,所以可以通過N組逆運算來確定最終的Key(當然如果密碼足夠長也可以達到此效果,因為key的長度是一定的[一直在循環使用]),未參與運算部分的key用號來代替(如果Key中有號呢,就用其它符號代替唄!)
測試了幾組對照的明文密文,得到如下key,
A***l*36*j*
Ac**lf****w
A*xy*f*65*w
Acxy*f3**j*
……
最終可以確定key值為:Acxylf365jw
看一看類似的可以編碼且可還原原碼的Base64編碼吧!
Base64的介紹請看:http://zh.wikipedia.org/zh-cn/Base64
Base64的邏輯是:字符串>>每個字符的8位二進制連接>>每6位轉換為十進制對應編碼表連接轉換后字符;如果要編碼的字節數不能被3整除,最后會多出1個或2個字節,那么可以使用下面的方法進行處理:先使用0字節值在末尾補足,使其能夠被3整除,然后再進行base64的編碼。在編碼后的base64文本后加上一個或兩個'='號,代表補足的字節數。
由于6位二進制最大為111111即63最小為000000所以編碼表中共有64個字符。
Base標準編碼表:
示例:
A ? QQ==
BC ?QkM=
正常情況下Base64的編碼表是:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=,但如果將編碼表中的字符換其它順序排一下結果又是什么樣子呢?
我使用了這個編碼表(等同于key):abcdefghijklmnopqrstuvwxyz0123456789+/=ABCDEFGHIJKLMNOPQRSTUVWXYZ,編碼字符:I Love You!
得到字符串:ssbm1Qz/if/I3se=,但使用標準編碼表解碼得到:???? 幾個“亂碼”…… 那么在已知編碼前字符和編碼后字符但編碼表不知的情況下能不能根據數組對照來確定編碼表呢?
答案也是肯定的:首先根據明文和密文的長度來確定使用的有沒有可能是Base64編碼(詳情Baidu、Google),然后再將明文轉換為8位二進制每6位再轉為十進制。
假如我們有明文:11CA467C5B1C3C0AB1D6C8A81104CC868CDC0A91 和密文:mtfdqtq2n0m1qJfdm0mWquiXrdzdoee4mteWnendody4q0rdmee5mq==那確定編碼表的過程如下:
00110001001100010100001101000001001101000011011000110111010000110011010101000010001100010100001100110011010000110011000001000001010000100011000101000100001101100100001100111000010000010011100000110001001100010011000000110100010000110100001100111000001101100011100001000011010001000100001100110000010000010011100100110001
12|19|5|3|16|19|16|54|13|52|12|53|16|35|5|3|12|52|12|48|16|20|8|49|17|3|25|3|14|4|4|56|12|19|4|48|13|4|13|3|14|3|24|56|16|52|17|3|12|4|4|57|12|16|
mtfdqtq2n0m1qJfdm0mWquiXrdzdoee4mteWnendody4q0rdmee5mq==
得到m在編碼表中的位置是12,t在編碼表中的位置是19,f在編碼表中的位置是5……(因后面的m都是12,t都是19……,所以可以確定這是換了編碼表的Base64)。在使用了多組明文和密文對照之后得到了變異的編碼表:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/= 至此成功完成了編碼表的逆算。
對于一次Base64編碼的的運算可以通過幾組對照的明、密文輕松逆得編碼表,假如是兩次使用同編碼表的Base64編碼,還有沒有可能得到編碼表呢?
可以做如下分析:(簡單的舉例,因第一個字符編碼后不受后面字符的影響,所以下面分析的是每一次編碼后的第一個字符)
第一次編碼后字符空間位于編碼表的8位至31位(可打印字符的ASSCII碼為32-126)
……
Bin(A)= 01000001 Base64(A)=編碼表中第16位
Bin(E)= 01000101 Base64(E)=編碼表中第17位
Bin(I)= 01001001 Base64(I)=編碼表中第18位
Bin(M)= 01001101 Base64(M)=編碼表中第19位
Bin(Q)= 01010001 Base64(Q)=編碼表中第20位
Bin(U)= 01010101 Base64(U)=編碼表中第21位
Bin(Y)= 01011001 Base64(Y)=編碼表中第22位
……
由于第一次編碼后字符范圍一定在下面這些字符范圍中:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=
其中ASSCII碼最小的為”43” 最大的為”122”
所以二次編碼后字符空間位于編碼表的第10位至30位。
由于二次編碼的結果是知道的,所以可以用多組密文來確定編碼表位于10-30之間的所有字符(但順序是不知道的)。
……
Base64(Base64(A))= Base64(編碼表中第16位的字符)
Base64(Base64(E))= Base64(編碼表中第17位的字符)
Base64(Base64(I))= Base64(編碼表中第18位的字符)
……
這樣編碼表10-30位所有字符的Base編碼結果都知道了(結果字符還位于10至30位之間)
由此得到了一個21元的方程(如果解的出來那么編碼表中的一部分字符的位置就確定了,不過還沒有嘗試能不能解出來……)
可見如果經過了二次編碼,要還原編碼表還是很困難的(大牛支招吧!)
本文只嘗試了從首字符去還原二次編碼的Base64編碼表,是不是有其它方法能很輕易的還原編碼表呢?
對于類似edcode(str,key)一次加密得到的密文且已知算法(密碼可還原)的情況下,是不是都可以通過明文和密文逆到key呢?