-
-
Save decay88/d57d40d3298245a03bff8c7505227286 to your computer and use it in GitHub Desktop.
XTEA encryption for C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Helper class for XTEA en/decryption of arbitrary data. | |
* | |
* Copyright (c) 2017, Henrik Heine | |
*/ | |
using System; | |
using System.IO; | |
using System.Text; | |
namespace InfectedBytes.Security { | |
/// <summary> | |
/// Implementation of the "eXtended Tiny Encryption Algorithm". | |
/// XTEA is a block cipher designed to correct weaknesses in TEA. | |
/// It is a 64-bit block Feistel cipher with a 128-bit key and suggested 64 rounds. | |
/// This algorithm is not as secure as AES or TripleDES, but because of it's small footprint it's a good choise for mobile applications. | |
/// </summary> | |
// ReSharper disable once InconsistentNaming | |
public static class XTEA { | |
/// <summary> | |
/// The recommended number of rounds is 32 and not 64, because each iteration performs two Feistel-cipher rounds. | |
/// </summary> | |
private const uint Rounds = 32; | |
/// <summary> | |
/// Encrypts the given data with the provided key. The result is Base64 encoded. | |
/// </summary> | |
/// <param name="data">The data to encrypt.</param> | |
/// <param name="key">The key used for encryption.</param> | |
/// <returns></returns> | |
public static string Encrypt(string data, string key) { | |
var dataBytes = Encoding.Unicode.GetBytes(data); | |
var keyBytes = Encoding.Unicode.GetBytes(key); | |
var result = Encrypt(dataBytes, keyBytes); | |
return Convert.ToBase64String(result); | |
} | |
/// <summary> | |
/// Decrypts the given data with the provided key. | |
/// Throws an exception if the length of the data array is not a multiple of 8. | |
/// Throws an exception if the decrypted length is longer than the actual array. | |
/// </summary> | |
/// <param name="data">The encrypted and Base64 encoded data.</param> | |
/// <param name="key">The key used for decryption.</param> | |
/// <returns></returns> | |
public static string Decrypt(string data, string key) { | |
var dataBytes = Convert.FromBase64String(data); | |
var keyBytes = Encoding.Unicode.GetBytes(key); | |
var result = Decrypt(dataBytes, keyBytes); | |
return Encoding.Unicode.GetString(result); | |
} | |
/// <summary> | |
/// Encrypts the given data with the provided key. | |
/// </summary> | |
/// <param name="data">The data to encrypt.</param> | |
/// <param name="key">The key used for encryption.</param> | |
/// <returns></returns> | |
public static byte[] Encrypt(byte[] data, byte[] key) { | |
var keyBuffer = CreateKey(key); | |
var blockBuffer = new uint[2]; | |
var result = new byte[NextMultipleOf8(data.Length + 4)]; | |
var lengthBuffer = BitConverter.GetBytes(data.Length); | |
Array.Copy(lengthBuffer, result, lengthBuffer.Length); | |
Array.Copy(data, 0, result, lengthBuffer.Length, data.Length); | |
using(var stream = new MemoryStream(result)) { | |
using(var writer = new BinaryWriter(stream)) { | |
for(int i = 0; i < result.Length; i += 8) { | |
blockBuffer[0] = BitConverter.ToUInt32(result, i); | |
blockBuffer[1] = BitConverter.ToUInt32(result, i + 4); | |
Encrypt(Rounds, blockBuffer, keyBuffer); | |
writer.Write(blockBuffer[0]); | |
writer.Write(blockBuffer[1]); | |
} | |
} | |
} | |
return result; | |
} | |
/// <summary> | |
/// Decrypts the given data with the provided key. | |
/// Throws an exception if the length of the data array is not a multiple of 8. | |
/// Throws an exception if the decrypted length is longer than the actual array. | |
/// </summary> | |
/// <param name="data">The encrypted data.</param> | |
/// <param name="key">The key used for decryption.</param> | |
/// <returns></returns> | |
public static byte[] Decrypt(byte[] data, byte[] key) { | |
if(data.Length % 8 != 0) throw new ArgumentException("Encrypted data length must be a multiple of 8 bytes."); | |
var keyBuffer = CreateKey(key); | |
var blockBuffer = new uint[2]; | |
var buffer = new byte[data.Length]; | |
Array.Copy(data, buffer, data.Length); | |
using(var stream = new MemoryStream(buffer)) { | |
using(var writer = new BinaryWriter(stream)) { | |
for(int i = 0; i < buffer.Length; i += 8) { | |
blockBuffer[0] = BitConverter.ToUInt32(buffer, i); | |
blockBuffer[1] = BitConverter.ToUInt32(buffer, i + 4); | |
Decrypt(Rounds, blockBuffer, keyBuffer); | |
writer.Write(blockBuffer[0]); | |
writer.Write(blockBuffer[1]); | |
} | |
} | |
} | |
// verify valid length | |
var length = BitConverter.ToUInt32(buffer, 0); | |
if(length > buffer.Length - 4) throw new ArgumentException("Invalid encrypted data"); | |
var result = new byte[length]; | |
Array.Copy(buffer, 4, result, 0, length); | |
return result; | |
} | |
private static int NextMultipleOf8(int length) { | |
// XTEA is a 64-bit block chiffre, therefore our data must be a multiple of 64 bit | |
return (length + 7) / 8 * 8; // this will give us the next multiple of 8 | |
} | |
/// <summary> | |
/// Transforms an key of arbitrary length to a 128 bit key. | |
/// </summary> | |
/// <param name="key"></param> | |
/// <returns></returns> | |
private static uint[] CreateKey(byte[] key) { | |
// It might be a better idea to just calculate the MD5 hash of the key: var hash = MD5.Create().ComputeHash(key); | |
// But we don't want to depend on the Cryptography namespace, because it would increase the build size for some Unity3d platforms. | |
var hash = new byte[16]; | |
for(int i = 0; i < key.Length; i++) { | |
hash[i % 16] = (byte)((31 * hash[i % 16]) ^ key[i]); | |
} | |
for(int i = key.Length; i < hash.Length; i++) { // if key was too short | |
hash[i] = (byte)(17 * i ^ key[i % key.Length]); | |
} | |
return new[] { | |
BitConverter.ToUInt32(hash, 0), BitConverter.ToUInt32(hash, 4), | |
BitConverter.ToUInt32(hash, 8), BitConverter.ToUInt32(hash, 12) | |
}; | |
} | |
#region Block Operations | |
/// <summary> | |
/// Performs an inplace encryption of the provided data array. | |
/// </summary> | |
/// <param name="rounds">The number of encryption rounds, the recommend value is 32.</param> | |
/// <param name="v">Data array containing two values.</param> | |
/// <param name="key">Key array containing 4 values.</param> | |
private static void Encrypt(uint rounds, uint[] v, uint[] key) { | |
uint v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9; | |
for(uint i = 0; i < rounds; i++) { | |
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); | |
sum += delta; | |
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); | |
} | |
v[0] = v0; | |
v[1] = v1; | |
} | |
/// <summary> | |
/// Performs an inplace decryption of the provided data array. | |
/// </summary> | |
/// <param name="rounds">The number of encryption rounds, the recommend value is 32.</param> | |
/// <param name="v">Data array containing two values.</param> | |
/// <param name="key">Key array containing 4 values.</param> | |
private static void Decrypt(uint rounds, uint[] v, uint[] key) { | |
uint v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * rounds; | |
for(uint i = 0; i < rounds; i++) { | |
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); | |
sum -= delta; | |
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); | |
} | |
v[0] = v0; | |
v[1] = v1; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment