Last active
May 6, 2019 11:48
-
-
Save JoshuaSullivan/92472caefc789554863d429764ad0b59 to your computer and use it in GitHub Desktop.
This playground shows how to use a RepeatingSequence type to create strong string obfuscation using a multi-byte nonce.
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
import Foundation | |
public struct ObfuscationDecoder { | |
public enum DeobfuscationError: Error { | |
case invalidStringBytes | |
} | |
/// The multi-byte nonce used to encode strings. | |
private static let nonce: [UInt8] = [199, 152, 254, 45, 85, 241, 134, 185, 22, 249, 182, 208, 43, 176, 143, 252] | |
/// The nonce converted into a RepeatingSequence. | |
private static let nonceSequence = RepeatingSequence(data: AnyCollection(nonce), maxCount: nil) | |
/// Converts an obfuscated byte array back into a String. | |
/// | |
/// - Parameter bytes: The obfuscated string bytes to be decoded. | |
/// - Returns: The decoded String. | |
/// - Throws: An .invalidStringBytes if the deobfuscated bytes could not be used to create a String. | |
public static func decode(bytes: [UInt8]) throws -> String { | |
let decodingSequence = zip(bytes, nonceSequence) | |
let decodedBytes = decodingSequence.map({$0 ^ $1}) | |
guard let string = String(bytes: decodedBytes, encoding: String.Encoding.utf8) else { | |
throw DeobfuscationError.invalidStringBytes | |
} | |
return string | |
} | |
} |
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
//: # String Obfuscator | |
//: This playground allows you to obfuscate sensitive strings such as API keys | |
//: using a multi-byte nonce. | |
import Foundation | |
//: A RepeatingSequence returns the elements of its underlying collection in a cyclical fashion | |
//: up to an optional maximum count. | |
//: - Note: You will need to add this type to your project in order to do the decoding step shown | |
//: at the bottom of the playground. | |
struct RepeatingSequence<T>: Sequence { | |
/// The Collection that we base our sequence on. We use a Collection and not | |
/// another Sequence because Sequences are not guaranteed to be repeatedly iterated. | |
let data: AnyCollection<T> | |
/// We can optionally specify a maximum number of iterations. This is necessary | |
/// to create non-infinite Sequences. | |
let maxCount: Int? | |
func makeIterator() -> AnyIterator<T> { | |
var index: AnyCollection.Index = data.startIndex | |
var count: Int = 0 | |
return AnyIterator<T> { | |
if let max = self.maxCount, count >= max { | |
return nil | |
} | |
defer { | |
index = self.data.index(after: index) | |
if index == self.data.endIndex { | |
index = self.data.startIndex | |
} | |
count += 1 | |
} | |
return self.data[index] | |
} | |
} | |
} | |
//: Replace this with the string you wish to encode. | |
let stringToEncode = "aBcDeFgHiJkLmNoPqRsTuVwXyZ" | |
print("String to encode: \(stringToEncode)\n") | |
//: Set `userNonce` to a specific value if you want to use a fixed nonce for your encoding. | |
//: Otherwise, a random one will be generated and printed out. | |
let userNonce: [UInt8]? = nil | |
let nonce: [UInt8] | |
if let un = userNonce { | |
nonce = un | |
} else { | |
/// Change this value up or down to change the length of the generated nonce. | |
let nonceLength = 16 | |
var nonceBytes: [UInt8] = [] | |
for i in 0..<nonceLength { | |
// Generate a sequence of random bytes. | |
nonceBytes.append(UInt8(arc4random_uniform(UInt32(UInt8.max)))) | |
} | |
print("Make note of this nonce or you will have no way to decode your string!") | |
print("Nonce: \(nonceBytes)\n") | |
nonce = nonceBytes | |
} | |
//: Create a repeating sequence and use zip2 to XOR it with the string bytes. | |
let repeatingNonce = RepeatingSequence(data: AnyCollection(nonce), maxCount: nil) | |
let encodingSequence = zip(stringToEncode.utf8, repeatingNonce) | |
let encodedBytes = encodingSequence.map({$0 ^ $1}) | |
print("Encoded bytes: \(encodedBytes)\n") | |
//: Now we'll verify that we can reverse the encoding. | |
let decodingSequence = zip(encodedBytes, repeatingNonce) | |
let decodedBytes = decodingSequence.map({ $0 ^ $1}) | |
guard let decodedString = String(bytes: decodedBytes, encoding: String.Encoding.utf8) else { | |
preconditionFailure("Unable to convert bytes back into a string!") | |
} | |
print("Decoded string: \(decodedString)") | |
print("Strings match: \(stringToEncode == decodedString)") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi. Under what license is this code under?