http://blog.chukong-inc.com/index.php/2012/02/14/利用tea算法進行數據加密/
TEA(Tiny Encryption Algorithm)是一種小型的對稱加密解密算法,最初是由劍橋計算機實驗室的 David Wheeler 和 Roger Needham 在 1994 年設計。采用128位密鑰,以8字節(64位)對數據分塊進行加密 / 解密。TEA特點是速度快、效率高,實現也非常簡單。
TEA出現后針對它的攻擊也不斷出現,在被發現存在缺陷后,TEA也發展出幾個版本,分別是XTEA、Block TEA和XXTEA。XTEA 跟 TEA 使用了相同的簡單運算,但它采用了截然不同的順序,為了阻止密鑰表攻擊,四個子密鑰(在加密過程中,原 128 位的密鑰被拆分為 4 個 32 位的子密鑰)采用了一種不太正規的方式進行混合,但速度更慢了。Block TEA 是XTEA算法的變種,它可以對 32 位大小任意倍數的變量塊進行操作。該算法將 XTEA 輪循函數依次應用於塊中的每個字,並且將它附加於它的鄰字。該操作重復多少輪依賴於塊的大小,但至少需要 6 輪。該方法的優勢在於它無需操作模式(CBC,OFB,CFB 等),密鑰可直接用於信息。對於長的信息它可能比 XTEA 更有效率。
在 1998 年,Markku-Juhani Saarinen 給出了一個可有效攻擊 Block TEA 算法的代碼,但之后很快 David J. Wheeler 和 Roger M. Needham 就給出了 Block TEA 算法的修訂版,這個算法被稱為 XXTEA。XXTEA 使用跟 Block TEA 相似的結構,但在處理塊中每個字時利用了相鄰字。它利用一個更復雜的 MX 函數代替了 XTEA 輪循函數。
XXTEA 算法很安全,而且非常快速,非常適合應用於 Web 及嵌入式系統開發中。 但名氣不大,采用的人比較少。我們在開發 iOS 系統程序時,由於大量的設定數據都是采用明文格式進行保存,容易被人分析修改。如現在常見的 iOS 非越獄機器上的內購應用破解,游戲數值修改等等。所以我們考慮利用XXTEA來對這些明文數據進行加密,來提高安全性。
英文 Wiki 上面提供了 XXTEA 的 C 語言實現,代碼非常簡單。
#define MX (z>>5^y<<2) + (y>>3^z<<4)^(sum^y) + (k[p&3^e]^z);
long btea(long* v, long n, long* k) {
unsigned long z=v[n-1], y=v[0], sum=0, e, DELTA=0x9e3779b9;
long p, q ;
if (n > 1) { /* Coding Part */
q = 6 + 52/n;
while (q-- > 0) {
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++) y = v[p+1], z = v[p] += MX;
y = v[0];
z = v[n-1] += MX;
}
return 0 ;
} else if (n < -1) { /* Decoding Part */
n = -n;
q = 6 + 52/n;
sum = q*DELTA ;
while (sum != 0) {
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--) z = v[p-1], y = v[p] -= MX;
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
return 0;
}
return 1;
}
函數把輸入的數據當成一個整型數組 long* v ,其長度為 long n,加密采用的密鑰也由數組傳入long* k。由於要求傳入的數據必須是整型對齊的,而我們平時應用中的數據長度不定。而且加密解密都在一個函數內進行操作,以長度 n 的值為正或負來進行區分也容易忽略,所以對它進行一下封裝。作成單獨的加密和解密函數
size_t XXTEAEncode(const unsigned char * pInputBuffer,
unsigned char * pOutputBuffer,
size_t nLength,
const_uint_ptr pUserDefineKey)
{
size_t nResult = 0;
if (pInputBuffer && pOutputBuffer && nLength > 0)
{
nResult = nLength / XXTEA_ALIGNMENT_BYTES +
(nLength % XXTEA_ALIGNMENT_BYTES ? 1 : 0);
memset(pOutputBuffer, 0, nResult * XXTEA_ALIGNMENT_BYTES);
memcpy(pOutputBuffer, pInputBuffer, nLength);
btea((uint32_t *)pOutputBuffer, nResult * 2, (uint32_t *)pUserDefineKey);
nResult *= XXTEA_ALIGNMENT_BYTES;
}
return nResult;
}
bool XXTEADecode(const unsigned char * pInputBuffer,
unsigned char * pOutputBuffer,
size_t nLength,
const_uint_ptr pUserDefineKey)
{
if(nLength %
return false;
bool result = false;
if(pInputBuffer && pOutputBuffer && nLength > 0)
{
int nSize = (nLength / XXTEA_ALIGNMENT_BYTES) * 2;
memset(pOutputBuffer, 0, nLength);
memcpy(pOutputBuffer, pInputBuffer, nLength);
btea((uint32_t *)pOutputBuffer, -nSize, (uint32_t *)pUserDefineKey);
result = true;
}
return result;
}
XXTEAEncode為加密函數,XXTEADecode為解密函數,他們的輸入參數相同,源數據指針pInputBuffer,輸出緩沖區指針pOutputBuffer,源數據長度nLength,和密鑰數組pUserDefineKey。這里輸出緩沖區的指針需要由調用者預先分配好,但是究竟需要多少內存呢?解密函數比較容易解決,因為解密后的數據肯定不會超過源數據長度,但是加密的時候由於需要將數據設置為整型對齊,數據可能長於輸入數據長度。我們模仿 Windows API 的做法,修改一下加密函數,在輸入參數中如果輸出緩沖區指針為空,返回需要的buffer長度:
size_t XXTEAEncode(const unsigned char * pInputBuffer,
unsigned char * pOutputBuffer,
size_t nLength,
const_uint_ptr pUserDefineKey)
{
size_t nResult = 0;
if (pInputBuffer && pOutputBuffer && nLength > 0)
{
nResult = nLength / XXTEA_ALIGNMENT_BYTES +
(nLength % XXTEA_ALIGNMENT_BYTES ? 1 : 0);
memset(pOutputBuffer, 0, nResult * XXTEA_ALIGNMENT_BYTES);
memcpy(pOutputBuffer, pInputBuffer, nLength);
btea((uint32_t *)pOutputBuffer, nResult * 2, (uint32_t *)pUserDefineKey);
nResult *= XXTEA_ALIGNMENT_BYTES;
}
else if(nLength > 0)
nResult = ((nLength / XXTEA_ALIGNMENT_BYTES) +
(nLength % XXTEA_ALIGNMENT_BYTES ? 1 : 0)) * XXTEA_ALIGNMENT_BYTES;
return nResult;
}
現在我們就可以通過加密函數來取得需要的 buffer 長度並且分配好內存,然后進行加密處理了。
來測試一下。首先聲明一個字符串 NSString stringTest = @”Hello XXTEA!”; 寫下這個字符串之后又想到一個問題。iOS系統中的字符是采用UTF-16編碼格式,也就是說所有的字符都是雙字節,和平常 C 語言下的 ANSI 字符不同,雖然一樣可以取得緩沖區指針進行加密,但是在iOS下我們需要處理的各種設定數據基本都是英文字符的文本,對於一個設置文件來說比ANSI字符多了一倍的空間,我們還是轉換一下讓英文字符恢復到8位編碼。查資料可以發現iOS系統不支持ANSI格式的雙字節字符編碼,我們選用UTF-8來兼容單、雙字節字符,同時節省空間。
NSString * testString = @"Hello XXTEA!";
int key[4] = {0x12345678, 0x734a67fc, 0xe367a642, 0x78432562};
int nSize = XXTEAEncode((const unsigned char *)[testString cStringUsingEncoding:NSUTF8StringEncoding], NULL, testString.length, key);
char * outBuffer = (char *)malloc(nSize);
XXTEAEncode((const unsigned char *)[testString cStringUsingEncoding:NSUTF8StringEncoding], (unsigned char *)outBuffer, ,nSize, key);
char *formatBuffer = (char *)malloc(128);
memset(formatBuffer, 0, 128);
HexToString((const char *)outBuffer, nSize, formatBuffer);
printf("%s\n\n", formatBuffer);
free(formatBuffer);
free(outBuffer);
運行上面的代碼得到輸出結果:2F49EF03665D18EF294E29A46AE17F7E 。我們再對這串字符進行下解密處理來驗證是否可以還原。
char * testData = "2F49EF03665D18EF294E29A46AE17F7E";
char * hexData = (char *)malloc(strlen(testData));
char * decryptBuffer = (char *)malloc(strlen(testData));
memset(hexData, 0, strlen(testData));
memset(decryptBuffer, 0, strlen(testData));
int nSize = StringToHex((const char *)testData, hexData);
XXTEADecode((const unsigned char *)hexData, (unsigned char *)decryptBuffer, nSize, key);
NSString * decodeString = [NSString stringWithCString:decryptBuffer encoding:NSUTF8StringEncoding];
NSLog(@"%@", decodeString);
free(decryptBuffer);
free(hexData);
在Xcode的Debug窗口中看到輸出信息:test[4559:f803] Hello XXTEA! 。解密完成。現在我們可以用XXTEA來處理明文文件,不會再被輕易篡改。