由「正方」jiam、jiemi之逆向思及Base64之逆編碼表

RedFree · 2014/01/06 11:02算法

申明:本文旨在技術交流,並不針對「正方」(新版正方教務系統密碼處理方式也換了,只是用這個作個引子而已……)!本文並無什麼深度,僅探討已知明文、密文和算法的狀況下逆向得Key的可能!編程

0x00 背景


常常遇到基友求助相似Base64編碼的解碼(先不說是否是Base64,下文會作說明)。如:p6FDpXlnQ1tHlLZK+NvA1hwfeND8NdXt1q6whqP6WODTEBP4UzzjnDQ== 這個很像Base64編碼吧,但你用標準Base64去解碼獲得的結果明顯不是想要的(這個有N多可能)。數組

0x01 編解碼


且從編碼、解碼提及吧,最簡單的編碼(爲何不叫加密、解密呢?看你用它幹什麼了……)是將字符映射,例如:ide

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) 。  
④ 更復雜的算法……(待補充) 
複製代碼

0x02 再議「正方」之jiam、jiemi


爲何是再議呢?由於正方教務系統的「加密」、「解密」運做方式早已被討論N屢次了,網上甚至能夠找到此算法的N多版本(可自行Baidu、Google)。 兩個典型的場景是:測試

① 有多組密文,已知幾組密文對應的明文和加密算法,可是因爲沒法得到加密所使用的Key,沒法解密其它密文。
② 由系統某缺陷能夠得到加密後的密文(固然明文是本身的密碼這個是可控的),得到了別人的密文後因爲無Key,沒法解密。
複製代碼

爲了研究此算法嘗試逆得Key,我決定再寫個VB版的。因爲事先已從網上查知算法位於/bin/zjdx.dll中,因此找起來就簡單了,網上找得一份「源碼」使用Reflector載入zjdx.dll,由登陸頁Default2跟進到jiam方法如圖:ui

2014010421455783468.jpg

源碼以下(已寫上註釋):編碼

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

0x03 反觀Base64編碼


看一看相似的能夠編碼且可還原原碼的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標準編碼表:

2014010421512778349.jpg

示例:

2014010421514440450.jpg

2014010421515961709.jpg
A   QQ==
BC  QkM=

正常狀況下Base64的編碼表是:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=,但若是將編碼表中的字符換其它順序排一下結果又是什麼樣子呢?

我使用了這個編碼表(等同於key):abcdefghijklmnopqrstuvwxyz0123456789+/=ABCDEFGHIJKLMNOPQRSTUVWXYZ,編碼字符:I Love You!

獲得字符串:ssbm1Qz/if/I3se=,但使用標準編碼表解碼獲得:ƦՌȞǀ 幾個「亂碼」…… 那麼在已知編碼前字符和編碼後字符但編碼表不知的狀況下能不能根據數組對照來肯定編碼表呢?

答案也是確定的:首先根據明文和密文的長度來肯定使用的有沒有多是Base64編碼(詳情Baidu、Google),而後再將明文轉換爲8位二進制每6位再轉爲十進制。

假如咱們有明文:11CA467C5B1C3C0AB1D6C8A81104CC868CDC0A91 和密文:mtfdqtq2n0m1qJfdm0mWquiXrdzdoee4mteWnendody4q0rdmee5mq==那肯定編碼表的過程以下:

Step1:明文轉爲8位二進制:

00110001001100010100001101000001001101000011011000110111010000110011010101000010001100010100001100110011010000110011000001000001010000100011000101000100001101100100001100111000010000010011100000110001001100010011000000110100010000110100001100111000001101100011100001000011010001000100001100110000010000010011100100110001

Step2:每6位二進制數轉爲十進制(我以|分割便於查看):

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|

Step3對應加密後字串:

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元的方程(若是解的出來那麼編碼表中的一部分字符的位置就肯定了,不過尚未嘗試能不能解出來……)

可見若是通過了二次編碼,要還原編碼表仍是很困難的(大牛支招吧!)

0x04 反思


本文只嘗試了從首字符去還原二次編碼的Base64編碼表,是否是有其它方法能很輕易的還原編碼表呢?

對於相似edcode(str,key)一次加密獲得的密文且已知算法(密碼可還原)的狀況下,是否是均可以經過明文和密文逆到key呢?

相關文章
相關標籤/搜索