Created
January 21, 2025 10:42
-
-
Save PWrzesinski/da156caead56116cf638f045c96d010f to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// I put this together from 2 sources: | |
// - https://github.com/cyrildever/feistel-cipher | |
// - https://stackoverflow.com/a/12590064/2786606 | |
// | |
// In particular, Daniel Vérité wrote a great answer on SO. | |
// | |
// I save this for myself, there are no guarantees about correctness, quality, or anything else. | |
import 'dart:convert'; | |
import 'package:crypto/crypto.dart'; | |
class _SplitString { | |
late String left; | |
late String right; | |
_SplitString(String data) { | |
final half = data.length ~/ 2; | |
left = data.substring(0, half); | |
right = data.substring(half); | |
} | |
} | |
class FeistelCipher { | |
static const _paddingCharacter = '0'; | |
static const _visuallyDistinctAlphabet = "abcdefghkmnoprstuwxzACDEFGHJKLMNPQRTUWXY3469"; | |
final String key; | |
final int rounds; | |
const FeistelCipher(this.key, this.rounds); | |
// String implementation | |
/// Obfuscate the passed data. | |
String encrypt(String data) { | |
if (data.length % 2 == 1) { | |
data = data.padLeft(data.length + 1, _paddingCharacter); | |
} | |
// Apply the balanced Feistel cipher. | |
final parts = _SplitString(data); | |
for (var i = 0; i < rounds; ++i) { | |
final tmp = xor(parts.left, round(parts.right, i)); | |
parts.left = parts.right; | |
parts.right = tmp; | |
} | |
return parts.left + parts.right; | |
} | |
/// Deobfuscate the passed data. | |
String decrypt(String obfuscated) { | |
if (obfuscated.length % 2 != 0) { | |
throw Exception('Invalid obfuscated data'); | |
} | |
// Apply the balanced Feistel cipher. | |
final parts = _SplitString(obfuscated); | |
for (var i = 0; i < rounds; ++i) { | |
final tmp = xor(parts.right, round(parts.left, rounds - i - 1)); | |
parts.right = parts.left; | |
parts.left = tmp; | |
} | |
final result = parts.left + parts.right; | |
if (result.startsWith(_paddingCharacter)) { | |
return result.substring(1); | |
} else { | |
return result; | |
} | |
} | |
String xor(String a, String b) { | |
final result = StringBuffer(); | |
for (var i = 0; i < a.length; ++i) { | |
result.write(String.fromCharCode(a.codeUnitAt(i) ^ b.codeUnitAt(i))); | |
} | |
return result.toString(); | |
} | |
String add(String a, String b) { | |
final result = StringBuffer(); | |
for (var i = 0; i < a.length; ++i) { | |
result.write(String.fromCharCode(a.codeUnitAt(i) + b.codeUnitAt(i))); | |
} | |
return result.toString(); | |
} | |
/// Returns an extraction of the passed string of the desired length from the passed start index. | |
/// If the desired length is too long, the key string is repeated. | |
String extract(String data, int index, int length) { | |
final startIndex = index % data.length; | |
final lengthNeeded = startIndex + length; | |
final repeatCount = (lengthNeeded / data.length).ceil(); | |
return (data * repeatCount).substring(startIndex, lengthNeeded); | |
} | |
String getSha256(String value) => sha256.convert(utf8.encode(value)).toString().toLowerCase(); | |
/// Round is the function applied at each round of the obfuscation process to the right side of the Feistel cipher. | |
String round(String item, int index) { | |
final addition = add(item, extract(key, index, item.length)); | |
final hashed = getSha256(addition); | |
return extract(hashed, index, item.length); | |
} | |
// int implementation | |
int roundInt(int value) { | |
return ((((1366 * value + 150889) % 714025) / 714025) * 32767).round(); | |
} | |
int encryptInt(int data) { | |
int left = data >> 16 & 0xFFFF; | |
int right = data & 0xFFFF; | |
int leftTemp, rightTemp; | |
for (var i = 0; i < rounds; ++i) { | |
leftTemp = right; | |
rightTemp = left ^ roundInt(right); | |
left = leftTemp; | |
right = rightTemp; | |
} | |
return (right << 16) + left; | |
} | |
String intToVisuallyDistinctString(int value, bool padTo3) { | |
const alphabet = _visuallyDistinctAlphabet; | |
const alphabetLength = alphabet.length; | |
int valueTemp = value; | |
final result = StringBuffer(); | |
while (valueTemp > 0) { | |
result.write(alphabet[valueTemp % alphabetLength]); | |
valueTemp ~/= alphabetLength; | |
} | |
final tempResult = result.toString(); | |
if (padTo3) { | |
return tempResult.padLeft(3, "7"); | |
} else { | |
return tempResult; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment