Last active
February 3, 2021 11:20
-
-
Save Sajjon/5e4ed39c32c76cb60925b512dd04bc7b to your computer and use it in GitHub Desktop.
ECIES Encryption now/simpler
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 XCTest | |
@testable import RadixSDK | |
class SimplerEncryptionTests: TestCase { | |
private lazy var angelaMerkel = KeyPair() | |
private lazy var joeBiden = KeyPair() | |
private lazy var justinTrudeau = KeyPair() | |
private lazy var vladimirPutin = KeyPair() | |
let message = "Guten tag Joe! My nukes are 100 miles south west of MΓΌnich, don't tell anyone" | |
let germany = ECIES() | |
let america = ECIES() | |
let russia = ECIES() | |
let canada = ECIES() | |
override func setUp() { | |
XCTAssertAllInequal([ | |
angelaMerkel, | |
joeBiden, | |
justinTrudeau, | |
vladimirPutin | |
]) | |
} | |
/// Current encryption solution. | |
/// | |
/// Use ECIES with an application layer ephemeral key (not to be confused with ephemeral key generated inside ECIES | |
/// algorithm itself!) as input to ECIES algorithm to encrypt message and then ECIES again with public key of each | |
/// party that should be able to decrypt the message to encrypt the application layer ephemeral public key. | |
/// | |
/// # Assumptions: | |
/// * Sender knows recipients public key, which she already does now, | |
/// since a Radix Address contains recipients public key. | |
/// | |
/// # Disadvantages: | |
/// * Larger atoms | |
/// * Would REQUIRE atom to contain encryption meta data (ECIES encrypted application layer ephemeral public key) | |
/// * Complex solution | |
/// | |
/// # Advantages: | |
/// * Sender can decrypt her own message + additional third parties can be specified to also be able to decrypt the message. | |
/// | |
func testCurrentEncryption() throws { | |
/// This is a copy paste of our actual current code in application layer, that performs encryption | |
/// and prepares the meta data needed to decrypt. | |
func currentComplicatedEncryption( | |
country ecies: ECIES, | |
messageToEncrypt message: String, | |
decryptableBy readers: [PublicKey] | |
) throws -> EncryptedMessageWithEncryptedSharedKey { | |
// Not to be confused with an ephemeral key that is generated inside the ECIES algorithm. | |
// This is also an ephemeral key, but this is used in the application layer (and as input to | |
// the ECIES algorithm, but it inside ECIES another ephemeral key is also generated...) | |
let applicationLayerEphemeralSharedKey = KeyPair() | |
let applicationLayerEphemeralSharedPrivateKeyEncryptedByEachReader = try readers.map { readerPublicKey in | |
try ecies.encrypt( | |
data: applicationLayerEphemeralSharedKey.privateKey, | |
using: readerPublicKey | |
) | |
} | |
let encryptedMessage = try ecies.encrypt( | |
byEncodingText: message, | |
publicKey: applicationLayerEphemeralSharedKey.publicKey | |
) | |
return EncryptedMessageWithEncryptedSharedKey( | |
applicationLayerEphemeralSharedPrivateKeyEncryptedByEachReader: applicationLayerEphemeralSharedPrivateKeyEncryptedByEachReader, | |
encryptedMessage: encryptedMessage | |
) | |
} | |
func currentComplicatedDecrypt( | |
country ecies: ECIES, | |
encryptedMessageWithEncryptedSharedKey: EncryptedMessageWithEncryptedSharedKey, | |
privateKey: PrivateKey | |
) throws -> String { | |
for privateKeyOfSharedEphemeralKeyEncryptedWithReaderPublicKey in encryptedMessageWithEncryptedSharedKey.applicationLayerEphemeralSharedPrivateKeyEncryptedByEachReader { | |
do { | |
let sharedEphemeralPrivateKeyData = try ecies.decrypt( | |
data: privateKeyOfSharedEphemeralKeyEncryptedWithReaderPublicKey, | |
using: privateKey | |
) | |
let sharedEphemeralPrivateKey = try PrivateKey(data: sharedEphemeralPrivateKeyData) | |
return try ecies.decryptAndDecode( | |
data: encryptedMessageWithEncryptedSharedKey.encryptedMessage, | |
using: sharedEphemeralPrivateKey | |
) | |
} catch { | |
// try next key... | |
} | |
} | |
throw ApplicationLayerDecryptionError.readerCannotDecrypt | |
} | |
// π©πͺπ©πͺπ©πͺ In Germany π©πͺπ©πͺπ©πͺ | |
// Angela Merkel encrypts message for Joe Biden | |
let encryptedMessageWithEncryptedSharedKey = try currentComplicatedEncryption( | |
country: germany, | |
messageToEncrypt: message, | |
decryptableBy: [angelaMerkel, joeBiden, justinTrudeau].map { $0.publicKey } | |
) | |
// Angela Merkel can decrypt her own message, which is nice! | |
XCTAssertEqual( | |
try currentComplicatedDecrypt( | |
country: germany, | |
encryptedMessageWithEncryptedSharedKey: encryptedMessageWithEncryptedSharedKey, | |
privateKey: angelaMerkel.privateKey | |
), | |
message | |
) | |
// ======= π°π°π° πππ π°π°π° ======= | |
// Atom sent over the wire to the Radix public network, now | |
// contains lots of data... here is JSON | |
/* | |
{ | |
"encryptedMessage" : "UMKHr3Z6Cxfn4mtJXyFk8iEDAqPwvEV04m6rJrUpWAGp0JE2yHwt6Xn3ysu22NiViREAAABQqWBMC3AGLuRHsepQjM\/gzLIjEJz aYl+H5iBRuX0ZF28PKEkiq9xCaxzois+YO99rygR5WH6KBT1KcPbcRfI3wxBaqgbs7lrQXGh+U1c\/votyAUizTnVr6t7OhZ\/B55 foXZd+LlW5oAiaN66wCOc5UQ==", | |
"protectors" : [ | |
"iEP42+EHk87KEunArWFggiEC2tunnR49rX6YhmSfF\/vN+oFL71wYNJXydhAsURjzATcAAAAwfB4gF\/uC5fzKpe+8vRxdCwO2Ey7C6ePp 57JoRjg2Xd5yxzyG4RzIVl+fJCwyO0PVJIc058v7oC4oYT5LxCDSNTJyYvTB0DYZsGXbKu959b8=", | |
"6eoLpbqj9s9L0ZKVtGPmxiEDCSCuU1RnUJGZyW6wwEWq8b25gqS0guletQceWSq3GpYAAAAwTfuZaacB5mLL7mgiy0RGftepG\/CuGD\/V DL\/JB7iXjpXhV260EKJuxrjHxD+2xEVLiM6AUzE5TBG47R6LYqHGHAPDvSHk\/2RO+XQB+BxTL3k=", | |
"jhBrJyKKrjEuTO7c9SE5miECLiSfegAbFkoCN+DfgO38R1Jot3wMsIVEO83Gh4yfDr0AAAAwFq0zInr3+M1up5yTCpJS1PdSGGidmLH8GR M9lGXTszq0RWO\/skxyNKzfgZLeNgB3xFnKrlQQcDG+J+AP2o2SiLVMbj\/GMPVntlKvE2QI4VE=" | |
] | |
} | |
*/ | |
// ======= π°π°π° πππ π°π°π° ======= | |
// πΊπΈπΊπΈπΊπΈ In the US πΊπΈπΊπΈπΊπΈ | |
// Joe Biden can indeed decrypt encrypted message from Angela | |
XCTAssertEqual( | |
try currentComplicatedDecrypt( | |
country: america, | |
encryptedMessageWithEncryptedSharedKey: encryptedMessageWithEncryptedSharedKey, | |
privateKey: joeBiden.privateKey | |
), | |
message | |
) | |
// π¨π¦π¨π¦π¨π¦ In Canada π¨π¦π¨π¦π¨π¦ | |
// Our ally Justin Trudeau can also decrypt the message since Angela specified that | |
XCTAssertEqual( | |
try currentComplicatedDecrypt( | |
country: canada, | |
encryptedMessageWithEncryptedSharedKey: encryptedMessageWithEncryptedSharedKey, | |
privateKey: justinTrudeau.privateKey | |
), | |
message | |
) | |
// π·πΊπ·πΊπ·πΊ In Russia π·πΊπ·πΊπ·πΊ | |
// Putin should not be able to decrypt the message | |
XCTAssertThrowsSpecificError( | |
try currentComplicatedDecrypt( | |
country: russia, | |
encryptedMessageWithEncryptedSharedKey: encryptedMessageWithEncryptedSharedKey, | |
privateKey: vladimirPutin.privateKey | |
), | |
ApplicationLayerDecryptionError.readerCannotDecrypt, | |
"Attacker Putin should not be able to decode message intended for someone else" | |
) | |
} | |
/// Cyons suggestion - simplest possible encryption still using ECIES | |
/// | |
/// Use ECIES encryption as is, but use recipients public key as input, | |
/// instead of a newly generated which gets encrypted and shared. | |
/// | |
/// # Assumptions: | |
/// * Sender knows recipients public key, which she already does now, | |
/// since a Radix Address contains recipients public key. | |
/// | |
/// # Disadvantages: | |
/// * Sender cannot decrypt her own message. | |
/// | |
/// # Advantages: | |
/// * No additional encryption (meta) data needed so ECIES does not need to change | |
/// * => simplest possible Atom. | |
/// | |
func testSimpleEncryption() throws { | |
// π©πͺπ©πͺπ©πͺ In Germany π©πͺπ©πͺπ©πͺ | |
// Angela Merkel encrypts message for Joe Biden | |
let encryptedMessage = try germany.encrypt( | |
byEncodingText: message, | |
publicKey: joeBiden.publicKey | |
) | |
// πΊπΈπΊπΈπΊπΈ In the US πΊπΈπΊπΈπΊπΈ | |
// Joe Biden can indeed decrypt encrypted message from Angela | |
let plainText = try america.decryptAndDecode( | |
data: encryptedMessage, | |
using: joeBiden.privateKey | |
) | |
XCTAssertEqual(plainText, message) | |
// π¨π¦π¨π¦π¨π¦ In Canada π¨π¦π¨π¦π¨π¦ | |
// Unfortunately we cannot allow our ally Justin Trudeau to be able to decrypt the message | |
Unfortunately(ally: justinTrudeau, cannotDecrypt: encryptedMessage) | |
// π·πΊπ·πΊπ·πΊ In Russia π·πΊπ·πΊπ·πΊ | |
// Putin should not be able to decrypt the message | |
Assert(vladimirPutin, cannotDecrypt: encryptedMessage) | |
// β οΈβ οΈβ οΈ Caveat with this simple solution β οΈβ οΈβ οΈ | |
// Unfortunately sender Angela cannot decrypt the message she just sent | |
Unfortunately(sender: angelaMerkel, cannotDecrypt: encryptedMessage) | |
} | |
} | |
private extension SimplerEncryptionTests { | |
func Unfortunately( | |
ally thirdParty: Signing, | |
cannotDecrypt encryptedMessage: Data, | |
_ file: StaticString = #file, | |
_ line: UInt = #line | |
) { | |
Assert(thirdParty, cannotDecrypt: encryptedMessage, file, line) | |
} | |
func Unfortunately( | |
sender thirdParty: Signing, | |
cannotDecrypt encryptedMessage: Data, | |
_ file: StaticString = #file, | |
_ line: UInt = #line | |
) { | |
Assert(thirdParty, cannotDecrypt: encryptedMessage, file, line) | |
} | |
func Assert( | |
_ thirdParty: Signing, | |
cannotDecrypt encryptedMessage: Data, | |
_ file: StaticString = #file, | |
_ line: UInt = #line | |
) { | |
XCTAssertThrowsSpecificError( | |
file: file, | |
line: line, | |
try ECIES().decrypt(data: encryptedMessage, using: thirdParty), | |
DecryptionError.macMismatch(expected: .irrelevant, butGot: .irrelevant), | |
"Third party should not be able to decode message intended for someone else" | |
) | |
} | |
} | |
enum ApplicationLayerDecryptionError: String, Swift.Error, Equatable { | |
case readerCannotDecrypt | |
} | |
struct EncryptedMessageWithEncryptedSharedKey { | |
let applicationLayerEphemeralSharedPrivateKeyEncryptedByEachReader: [Data] | |
let encryptedMessage: Data | |
} | |
private let encoding: String.Encoding = .utf8 | |
extension ECIES { | |
func encrypt( | |
byEncodingText plainText: String, | |
publicKey: PublicKey | |
) throws -> Data { | |
let encoded = plainText.toData(encodingForced: encoding) | |
return try encrypt(data: encoded, using: publicKey) | |
} | |
func decryptAndDecode( | |
data dataConvertible: DataConvertible, | |
using privateKey: Signing | |
) throws -> String { | |
let decrypted = try decrypt(data: dataConvertible, using: privateKey) | |
return String(data: decrypted, encoding: encoding)! | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment