-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/comment_encrypt'
- Loading branch information
Showing
11 changed files
with
407 additions
and
1 deletion.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import Foundation | ||
|
||
public struct CommentDecryptor { | ||
enum Error: Swift.Error { | ||
case incorrectCipherData | ||
case incorrectCipherDataLength | ||
case failedGetSharedSecret(error: Swift.Error) | ||
case incorrectEncryptedDataSize | ||
case incorrectHash | ||
case incorrectPrefixSize | ||
} | ||
|
||
private let privateKey: PrivateKey | ||
private let publicKey: PublicKey | ||
private let cipherText: String | ||
private let senderAddress: Address | ||
|
||
public init(privateKey: PrivateKey, | ||
publicKey: PublicKey, | ||
cipherText: String, | ||
senderAddress: Address) { | ||
self.privateKey = privateKey | ||
self.publicKey = publicKey | ||
self.cipherText = cipherText | ||
self.senderAddress = senderAddress | ||
} | ||
|
||
public func decrypt() throws -> String? { | ||
guard let cipherTextData = Data(hex: cipherText) else { | ||
throw Error.incorrectCipherData | ||
} | ||
|
||
let cipherTextBytes = [UInt8](cipherTextData) | ||
guard cipherTextBytes.count >= publicKey.data.count else { | ||
throw Error.incorrectCipherDataLength | ||
} | ||
let cipherTextPublicKeyBytes = Array(cipherTextBytes[0..<publicKey.data.count]) | ||
let encryptedData = Array(cipherTextBytes[publicKey.data.count..<cipherTextBytes.count]) | ||
guard encryptedData.count >= 16, encryptedData.count % 16 == 0 else { | ||
throw Error.incorrectEncryptedDataSize | ||
} | ||
|
||
let senderPublicKey = PublicKey(data: Data([UInt8](publicKey.data).enumerated().map { $0.element ^ cipherTextPublicKeyBytes[$0.offset] })) | ||
let sharedSecret: Data | ||
do { | ||
sharedSecret = try Ed25519.getSharedSecret(privateKey: privateKey, publicKey: senderPublicKey) | ||
} catch { | ||
throw Error.failedGetSharedSecret(error: error) | ||
} | ||
|
||
let msgKey = Data(Array(encryptedData[0..<16])) | ||
let data = Data(Array(encryptedData[16..<encryptedData.count])) | ||
|
||
let cbcStateSecret = HMAC_SHA512.hmacSha512(message: msgKey, key: sharedSecret) | ||
|
||
let aesKey = cbcStateSecret[0..<32] | ||
let iv = cbcStateSecret[32..<48] | ||
let decryptedData = try AES_CBC(key: aesKey, iv: iv).decrypt(cipherData: data) | ||
|
||
let salt = senderAddress.toFriendly(testOnly: false, bounceable: true).toString().data(using: .utf8) ?? Data() | ||
|
||
let decryptedDataHash = HMAC_SHA512.hmacSha512(message: decryptedData, key: salt) | ||
|
||
let encryptionMsgKey = decryptedDataHash[0..<16] | ||
guard encryptionMsgKey == msgKey else { | ||
throw Error.incorrectHash | ||
} | ||
|
||
let prefixLength = Int(decryptedData[0]) | ||
guard prefixLength <= decryptedData.count && prefixLength >= 16 else { | ||
throw Error.incorrectPrefixSize | ||
} | ||
|
||
let decryptedMessage = decryptedData[prefixLength..<decryptedData.count] | ||
return String(data: decryptedMessage, encoding: .utf8) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import Foundation | ||
|
||
public struct CommentEncryptor { | ||
enum Error: Swift.Error { | ||
case commentIsEmpty | ||
case failedGetSharedSecret(error: Swift.Error) | ||
case incorrectDataSize | ||
} | ||
|
||
private let comment: String | ||
private let senderPublicKey: PublicKey | ||
private let senderPrivateKey: PrivateKey | ||
private let peerPublicKey: PublicKey | ||
private let senderAddress: Address | ||
|
||
public init(comment: String, | ||
senderPublicKey: PublicKey, | ||
senderPrivateKey: PrivateKey, | ||
peerPublicKey: PublicKey, | ||
senderAddress: Address) { | ||
self.comment = comment | ||
self.senderPublicKey = senderPublicKey | ||
self.senderPrivateKey = senderPrivateKey | ||
self.peerPublicKey = peerPublicKey | ||
self.senderAddress = senderAddress | ||
} | ||
|
||
public func encrypt() throws -> Data { | ||
guard !comment.isEmpty else { | ||
throw Error.commentIsEmpty | ||
} | ||
|
||
let commentData = comment.data(using: .utf8) ?? Data() | ||
let salt = senderAddress.toFriendly(testOnly: false, bounceable: true).toString().data(using: .utf8) ?? Data() | ||
|
||
let sharedSecret: Data | ||
do { | ||
sharedSecret = try Ed25519.getSharedSecret(privateKey: senderPrivateKey, publicKey: peerPublicKey) | ||
} catch { | ||
throw Error.failedGetSharedSecret(error: error) | ||
} | ||
|
||
let prefix = try getRandomPrefix(dataLength: commentData.count, minPadding: 16) | ||
let data = prefix + commentData | ||
|
||
guard data.count % 16 == 0 else { throw Error.incorrectDataSize } | ||
|
||
let dataHash = HMAC_SHA512.hmacSha512(message: data, key: salt) | ||
let msgKey = dataHash[0..<16] | ||
let cbcStateSecret = HMAC_SHA512.hmacSha512(message: msgKey, key: sharedSecret) | ||
|
||
let aesKey = cbcStateSecret[0..<32] | ||
let iv = cbcStateSecret[32..<48] | ||
let encrypted = try AES_CBC(key: aesKey, iv: iv).encrypt(data: data) | ||
|
||
let encryptedData = msgKey + encrypted | ||
|
||
let cipherTextPrefix = peerPublicKey.data.enumerated().map { $0.element ^ senderPublicKey.data[$0.offset] } | ||
let cipherTextData = Data(cipherTextPrefix + encryptedData) | ||
|
||
return cipherTextData | ||
} | ||
|
||
private func getRandomPrefix(dataLength: Int, minPadding: Int) throws -> Data { | ||
let prefixLength = ((minPadding + 15 + dataLength) & -16) - dataLength | ||
var prefix = try RandomBytes.generate(length: prefixLength) | ||
prefix[0] = withUnsafeBytes(of: prefixLength.littleEndian, Array.init)[0] | ||
return prefix | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Source/TonSwift/CommentEncryption/EncryptedCommentCellBuilder.swift
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import Foundation | ||
|
||
public struct EncryptedCommentCellBuilder { | ||
public static func buildCell(encryptedData: Data) throws -> Cell { | ||
let opCodeData = Data(withUnsafeBytes(of: OpCodes.ENCRYPTED_COMMENT.bigEndian, Array.init)) | ||
let payloadData = opCodeData + encryptedData | ||
|
||
let builder = Builder() | ||
return try builder.writeSnakeData(payloadData).endCell() | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
public enum Ed25519 { | ||
public enum Error: Swift.Error { | ||
case sharedSecretError(Swift.Error) | ||
} | ||
|
||
public static func getSharedSecret(privateKey: PrivateKey, publicKey: PublicKey) throws -> Data { | ||
do { | ||
let xPrivateKey = try privateKey.toX25519 | ||
let xPublicKey = try publicKey.toX25519 | ||
return try X25519.getSharedSecret(privateKey: xPrivateKey, publicKey: xPublicKey) | ||
} catch { | ||
throw Error.sharedSecretError(error) | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import Foundation | ||
import Clibsodium | ||
|
||
public enum X25519 { | ||
enum Error: Swift.Error { | ||
case sharedSecretError(code: Int) | ||
} | ||
|
||
public struct PrivateKey: Key, Equatable, Codable { | ||
public let data: Data | ||
|
||
public init(data: Data) { | ||
self.data = data | ||
} | ||
} | ||
|
||
public struct PublicKey: Key, Equatable, Codable { | ||
public let data: Data | ||
|
||
public init(data: Data) { | ||
self.data = data | ||
} | ||
} | ||
|
||
static func getSharedSecret(privateKey: X25519.PrivateKey, publicKey: X25519.PublicKey) throws -> Data { | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: .sharedSecretLength) | ||
try privateKey.data.withUnsafeBytes { bufferPointer in | ||
guard let privateKeyPointer = bufferPointer.baseAddress else { return } | ||
try publicKey.data.withUnsafeBytes { bufferPointer in | ||
guard let publicKeyPointer = bufferPointer.baseAddress else { return } | ||
let statusCode = crypto_scalarmult(&outputBuffer, privateKeyPointer, publicKeyPointer) | ||
guard statusCode == 0 else { | ||
throw Error.sharedSecretError(code: Int(statusCode)) | ||
} | ||
} | ||
} | ||
return Data(outputBuffer) | ||
} | ||
} | ||
|
||
public extension X25519 { | ||
enum X25519ConversionError: Swift.Error { | ||
case publicKeyConversionFailed(code: Int) | ||
case privateKeyConversionFailed(code: Int) | ||
} | ||
} | ||
|
||
extension PublicKey { | ||
var toX25519: X25519.PublicKey { | ||
get throws { | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: .publicKeyLength) | ||
try data.withUnsafeBytes { buffer in | ||
guard let pointer = buffer.baseAddress else { return } | ||
let statusCode = crypto_sign_ed25519_pk_to_curve25519(&outputBuffer, pointer) | ||
guard statusCode == 0 else { | ||
throw X25519.X25519ConversionError.publicKeyConversionFailed(code: Int(statusCode)) | ||
} | ||
} | ||
return X25519.PublicKey(data: Data(outputBuffer)) | ||
} | ||
} | ||
} | ||
|
||
extension PrivateKey { | ||
var toX25519: X25519.PrivateKey { | ||
get throws { | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: .privateKeyLength) | ||
try data.withUnsafeBytes { buffer in | ||
guard let pointer = buffer.baseAddress else { return } | ||
let statusCode = crypto_sign_ed25519_sk_to_curve25519(&outputBuffer, pointer) | ||
guard statusCode == 0 else { | ||
throw X25519.X25519ConversionError.privateKeyConversionFailed(code: Int(statusCode)) | ||
} | ||
} | ||
return X25519.PrivateKey(data: Data(outputBuffer)) | ||
} | ||
} | ||
} | ||
|
||
private extension Int { | ||
static let privateKeyLength: Int = 32 | ||
static let publicKeyLength: Int = 32 | ||
static let sharedSecretLength: Int = 32 | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import Foundation | ||
import CommonCrypto | ||
|
||
public struct AES_CBC { | ||
enum Error: Swift.Error { | ||
case encryptionError(status: CCCryptorStatus) | ||
case decryptionError(status: CCCryptorStatus) | ||
} | ||
|
||
public let key: Data | ||
public let iv: Data | ||
|
||
public init(key: Data, | ||
iv: Data) { | ||
self.key = key | ||
self.iv = iv | ||
} | ||
|
||
public func decrypt(cipherData: Data) throws -> Data { | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: cipherData.count + kCCBlockSizeAES128) | ||
var numBytesDecrypted = 0 | ||
|
||
let status = CCCrypt(CCOperation(kCCDecrypt), | ||
CCAlgorithm(kCCAlgorithmAES), | ||
CCOptions(kCCOptionPKCS7Padding), | ||
Array(key), | ||
kCCKeySizeAES256, | ||
Array(iv), | ||
Array(cipherData), | ||
cipherData.count, | ||
&outputBuffer, | ||
outputBuffer.count, | ||
&numBytesDecrypted) | ||
|
||
guard status == kCCSuccess else { | ||
throw Error.decryptionError(status: status) | ||
} | ||
|
||
let outputBytes = outputBuffer.prefix(numBytesDecrypted) | ||
return Data(outputBytes) | ||
} | ||
|
||
public func encrypt(data: Data) throws -> Data { | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: data.count + kCCBlockSizeAES128) | ||
var numBytesEncrypted = 0 | ||
|
||
let status = CCCrypt(CCOperation(kCCEncrypt), | ||
CCAlgorithm(kCCAlgorithmAES), | ||
CCOptions(kCCOptionPKCS7Padding), | ||
Array(key), | ||
kCCKeySizeAES256, | ||
Array(iv), | ||
Array(data), | ||
data.count, | ||
&outputBuffer, | ||
outputBuffer.count, | ||
&numBytesEncrypted) | ||
|
||
guard status == kCCSuccess else { | ||
throw Error.encryptionError(status: status) | ||
} | ||
let outputBytes = outputBuffer.prefix(numBytesEncrypted) | ||
return Data(outputBytes) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Foundation | ||
import CommonCrypto | ||
|
||
public struct HMAC_SHA512 { | ||
public static func hmacSha512(message: Data, key: Data) -> Data { | ||
let count = Int(CC_SHA512_DIGEST_LENGTH) | ||
var outputBuffer = Array<UInt8>(repeating: 0, count: count) | ||
|
||
key.withUnsafeBytes { bufferPointer in | ||
guard let keyPointer = bufferPointer.baseAddress else { return } | ||
message.withUnsafeBytes { bufferPointer in | ||
guard let messagePointer = bufferPointer.baseAddress else { return } | ||
CCHmac( | ||
CCHmacAlgorithm( | ||
kCCHmacAlgSHA512 | ||
), | ||
keyPointer, | ||
key.count, | ||
messagePointer, | ||
message.count, | ||
&outputBuffer | ||
) | ||
} | ||
} | ||
return Data(bytes: outputBuffer, count: count) | ||
} | ||
} | ||
|
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
Oops, something went wrong.