Skip to content

Instantly share code, notes, and snippets.

@Sajjon
Last active February 3, 2021 11:20
Show Gist options
  • Save Sajjon/5e4ed39c32c76cb60925b512dd04bc7b to your computer and use it in GitHub Desktop.
Save Sajjon/5e4ed39c32c76cb60925b512dd04bc7b to your computer and use it in GitHub Desktop.
ECIES Encryption now/simpler
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