Random Thoughts by Fabien Penso

Swift CryptoKit and Ruby/Python

I spent days figuring out how to decrypt ChaChaPoly encrypted data with Swift CryptoKit using other languages. What should have taken me minutes took me hours. As a time savior, here is how you can decrypt it using Ruby or Python. I ended up reading the source code of swift-crypto to understand what’s the combined sealbox was doing.

Use the following in Xcode Playground to encrypt a string:

import UIKit
import CryptoKit

let str = "Hello, playground"
let strData = str.data(using: .utf8)!

let key = SymmetricKey(size: .bits256)
let keyString = key.withUnsafeBytes { Data($0) }.base64EncodedString()

do {
    let sealbox = try ChaChaPoly.seal(strData, using: key)

    print("Key: \(keyString)")
    print("Combined: \(sealbox.combined.base64EncodedString())")
} catch { }

The output when running it on my computer (you obviously will get a different result):

Key: j6tifPZTjUtGoz+1RJkO8dOMlu48MUUSlwACw/fCBw0=
Combined: OWFsadrLrBc6ak+6TiYhAI6JKvoQzVMpnRdJ6iE5vEiAhadrCu6EcEQiAs7G

You can decrypt it using Ruby with:

#!/usr/bin/env ruby

require "openssl"
require "base64"

key = Base64.decode64 "j6tifPZTjUtGoz+1RJkO8dOMlu48MUUSlwACw/fCBw0="
combined = Base64.decode64 "OWFsadrLrBc6ak+6TiYhAI6JKvoQzVMpnRdJ6iE5vEiAhadrCu6EcEQiAs7G"

text = "Hello, playground"

combinedTag = combined[-16..-1]
combinedNonce = combined[0..11]
combinedCipherText = combined[12..(combined.size-17)]

decipher = OpenSSL::Cipher.new("chacha20-poly1305").decrypt
decipher.key = key
decipher.iv = combinedNonce
decipher.auth_tag = combinedTag

decrypted = decipher.update(combinedCipherText) + decipher.final
if decrypted == text
  puts "OK!"

And you can decrypt it using Python:

#!/usr/bin/env python3
# Installation:
# pip install pycryptodome

import json
import Crypto
from base64 import b64decode
from Crypto.Cipher import ChaCha20_Poly1305

key = b64decode("j6tifPZTjUtGoz+1RJkO8dOMlu48MUUSlwACw/fCBw0=")
combined = b64decode("OWFsadrLrBc6ak+6TiYhAI6JKvoQzVMpnRdJ6iE5vEiAhadrCu6EcEQiAs7G")

text = "Hello, playground"

combinedNonce = combined[:12]
combinedTag = combined[:-16]
combinedCipher = combined[12:-16]
decrypted = ChaCha20_Poly1305.new(key=key, nonce=combinedNonce).decrypt(combinedCipher).decode()

if decrypted == text:

I hope this post saved you some time. Thanks to @FredericJacobs for the help.