Skip to content

Instantly share code, notes, and snippets.

@decay88
Forked from InfectedBytes/XTEA.cs
Created September 26, 2019 14:41
Show Gist options
  • Save decay88/d57d40d3298245a03bff8c7505227286 to your computer and use it in GitHub Desktop.
Save decay88/d57d40d3298245a03bff8c7505227286 to your computer and use it in GitHub Desktop.
XTEA encryption for C#
/*
* 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