先介绍一下XXTEA算法的相关知识:
TEA(Tiny Encryption Algorithm)是一种小型的对称加密解密算法,支持128位密码,与BlowFish一样TEA每次只能加密/解密8字节数据。TEA特点是速度快、效率高,实现也非常简单。由于针对TEA的攻击不断出现,所以TEA也发展出几个版本,分别是XTEA、Block TEA和XXTEA。
TEA加密和解密时都使用一个常量值,这个常量值为0x9e3779b,这个值是近似黄金分割率,注意,有些编程人员为了避免在程序中直接出现"mov 变量,0x9e3779b",以免被破解者直接搜索0x9e3779b这个常数得知使用TEA算法,所以有时会使用"sub 变量,0x61C88647"代替"mov 变量,0x9e3779b",0x61C88647=-(0x9e3779b)。
TEA算法每一次可以操作64bit(8byte),采用128bit(16byte)作为key,算法采用迭代的形式,推荐的迭代轮数是64轮,最少32轮。
标准的16轮运算TEA,如果要改成标准的32轮运算TEA,只需修改code和decode中的n为32,并将decode中的delta左移4位改成左移5位即可。
XTEA 跟 TEA 使用了相同的简单运算,但它采用了截然不同的顺序,为了阻止密钥表攻击,四个子密钥(在加密过程中,原 128 位的密钥被拆分为 4 个 32 位的子密钥)采用了一种不太正规的方式进行混合,但速度更慢了。在跟描述 XTEA 算法的同一份报告中,还介绍了另外一种被称为 Block TEA 算法的变种,它可以对 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 轮循函数,MX 使用 2 个输入量。
如果加密字符串长度不是 4 的整数倍,则这些实现的在加密后无法真正还原,还原以后的字符串实际上与原字符串不相等,而是后面多了一些 \0 的字符,或者少了一些 \0 的字符。原因在于 XXTEA 算法只定义了如何对 32 位的信息块数组(实际上是 32 位无符号整数数组)进行加密,而并没有定义如何来将字符串编码为这种数组。而现有的实现中在将字符串编码为整数数组时,都丢失了字符串长度信息,因此还原出现了问题。
原理图:
下面是我的实现,使用这个实现你不用担心上面提到的无法还原的情况:
有两个版本C#版和JAVA版,两个完全兼容。
C#版:
using System;
using System.Collections.Generic;
using System.Text;
namespace Lidroid.Utilities
{
public static class XXTEA
{
public static string Encrypt(this string data, string key)
{
return TEAEncrypt(
Encoding.UTF8.GetBytes(data.PadRight(MIN_LENGTH, SPECIAL_CHAR)).ToLongArray(),
Encoding.UTF8.GetBytes(key.PadRight(MIN_LENGTH, SPECIAL_CHAR)).ToLongArray()).ToHexString();
}
public static string Decrypt(this string data, string key)
{
if (string.IsNullOrWhiteSpace(data)) { return data; }
byte[] code = TEADecrypt(
data.ToLongArray(),
Encoding.UTF8.GetBytes(key.PadRight(MIN_LENGTH, SPECIAL_CHAR)).ToLongArray()).ToByteArray();
return Encoding.UTF8.GetString(code, 0, code.Length);
}
private static long[] TEAEncrypt(long[] data, long[] key)
{
int n = data.Length;
if (n < 1) { return data; }
long z = data[data.Length - 1], y = data[0], sum = 0, e, p, q;
q = 6 + 52 / n;
while (q-- > 0)
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
{
y = data[p + 1];
z = data[p] += (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (key[p & 3 ^ e] ^ z);
}
y = data[0];
z = data[n - 1] += (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (key[p & 3 ^ e] ^ z);
}
return data;
}
private static long[] TEADecrypt(long[] data, long[] key)
{
int n = data.Length;
if (n < 1) { return data; }
long z = data[data.Length - 1], y = data[0], sum = 0, e, p, q;
q = 6 + 52 / n;
sum = q * DELTA;
while (sum != 0)
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = data[p - 1];
y = data[p] -= (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (key[p & 3 ^ e] ^ z);
}
z = data[n - 1];
y = data[0] -= (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (key[p & 3 ^ e] ^ z);
sum -= DELTA;
}
return data;
}
private static long[] ToLongArray(this byte[] data)
{
int n = (data.Length % 8 == 0 ? 0 : 1) + data.Length / 8;
long[] result = new long[n];
for (int i = 0; i < n - 1; i++)
{
result[i] = BitConverter.ToInt64(data, i * 8);
}
byte[] buffer = new byte[8];
Array.Copy(data, (n - 1) * 8, buffer, 0, data.Length - (n - 1) * 8);
result[n - 1] = BitConverter.ToInt64(buffer, 0);
return result;
}
private static byte[] ToByteArray(this long[] data)
{
List<byte> result = new List<byte>(data.Length * 8);
for (int i = 0; i < data.Length; i++)
{
result.AddRange(BitConverter.GetBytes(data[i]));
}
while (result[result.Count - 1] == SPECIAL_CHAR)
{
result.RemoveAt(result.Count - 1);
}
return result.ToArray();
}
private static string ToHexString(this long[] data)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.Append(data[i].ToString("x2").PadLeft(16, '0'));
}
return sb.ToString();
}
private static long[] ToLongArray(this string data)
{
int len = data.Length / 16;
long[] result = new long[len];
for (int i = 0; i < len; i++)
{
result[i] = Convert.ToInt64(data.Substring(i * 16, 16), 16);
}
return result;
}
private const long DELTA = 0x9E3779B9;
private const int MIN_LENGTH = 32;
private const char SPECIAL_CHAR = '\0';
}
}
JAVA版:
package Lidroid.Utilities;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/*
* XXTEA 加密算法
*/
public class XXTEA {
public static String Encrypt(String data, String key) {
return ToHexString(TEAEncrypt(
ToLongArray(PadRight(data, MIN_LENGTH).getBytes(
Charset.forName("UTF8"))),
ToLongArray(PadRight(key, MIN_LENGTH).getBytes(
Charset.forName("UTF8")))));
}
public static String Decrypt(String data, String key) {
if (data == null || data.length() < MIN_LENGTH) {
return data;
}
byte[] code = ToByteArray(TEADecrypt(
ToLongArray(data),
ToLongArray(PadRight(key, MIN_LENGTH).getBytes(
Charset.forName("UTF8")))));
return new String(code, Charset.forName("UTF8"));
}
private static long[] TEAEncrypt(long[] data, long[] key) {
int n = data.length;
if (n < 1) {
return data;
}
long z = data[data.length - 1], y = data[0], sum = 0, e, p, q;
q = 6 + 52 / n;
while (q-- > 0) {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = data[(int) (p + 1)];
z = data[(int) p] += (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)
^ (sum ^ y) + (key[(int) (p & 3 ^ e)] ^ z);
}
y = data[0];
z = data[n - 1] += (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)
^ (sum ^ y) + (key[(int) (p & 3 ^ e)] ^ z);
}
return data;
}
private static long[] TEADecrypt(long[] data, long[] key) {
int n = data.length;
if (n < 1) {
return data;
}
long z = data[data.length - 1], y = data[0], sum = 0, e, p, q;
q = 6 + 52 / n;
sum = q * DELTA;
while (sum != 0) {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = data[(int) (p - 1)];
y = data[(int) p] -= (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)
^ (sum ^ y) + (key[(int) (p & 3 ^ e)] ^ z);
}
z = data[n - 1];
y = data[0] -= (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y)
+ (key[(int) (p & 3 ^ e)] ^ z);
sum -= DELTA;
}
return data;
}
private static long[] ToLongArray(byte[] data) {
int n = (data.length % 8 == 0 ? 0 : 1) + data.length / 8;
long[] result = new long[n];
for (int i = 0; i < n - 1; i++) {
result[i] = bytes2long(data, i * 8);
}
byte[] buffer = new byte[8];
for (int i = 0, j = (n - 1) * 8; j < data.length; i++, j++) {
buffer[i] = data[j];
}
result[n - 1] = bytes2long(buffer, 0);
return result;
}
private static byte[] ToByteArray(long[] data) {
List<Byte> result = new ArrayList<Byte>();
for (int i = 0; i < data.length; i++) {
byte[] bs = long2bytes(data[i]);
for (int j = 0; j < 8; j++) {
result.add(bs[j]);
}
}
while (result.get(result.size() - 1) == SPECIAL_CHAR) {
result.remove(result.size() - 1);
}
byte[] ret = new byte[result.size()];
for (int i = 0; i < ret.length; i++) {
ret[i] = result.get(i);
}
return ret;
}
public static byte[] long2bytes(long num) {
ByteBuffer buffer = ByteBuffer.allocate(8).order(
ByteOrder.LITTLE_ENDIAN);
buffer.putLong(num);
return buffer.array();
}
public static long bytes2long(byte[] b, int index) {
ByteBuffer buffer = ByteBuffer.allocate(8).order(
ByteOrder.LITTLE_ENDIAN);
buffer.put(b, index, 8);
return buffer.getLong(0);
}
private static String ToHexString(long[] data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i++) {
sb.append(PadLeft(Long.toHexString(data[i]), 16));
}
return sb.toString();
}
private static long[] ToLongArray(String data) {
int len = data.length() / 16;
long[] result = new long[len];
for (int i = 0; i < len; i++) {
result[i] = new BigInteger(data.substring(i * 16, i * 16 + 16), 16)
.longValue();
}
return result;
}
private static String PadRight(String source, int length) {
while (source.length() < length) {
source += SPECIAL_CHAR;
}
return source;
}
private static String PadLeft(String source, int length) {
while (source.length() < length) {
source = '0' + source;
}
return source;
}
private static long DELTA = 2654435769L;
private static int MIN_LENGTH = 32;
private static char SPECIAL_CHAR = '\0';
}
两个版本XXTEA算法都只有Encrypt和Decrypt是公开的方法,使用起来非常方便。