-
Notifications
You must be signed in to change notification settings - Fork 116
A JSON Web Encryption (JWE) represents encrypted content. It provides confidentiality and optionally integrity protection for this content JWE consists of:
- A JSON-based header
- An encrypted content encryption key
- An initialization vector
- Some additional authenticated data
- The ciphertext
- An authentication tag
To understand a JWE, it is good to understand the way it encrypts its payload.
As you may have noticed, a JWE contains two encrypted parts. The encrypted content encryption key and the ciphertext. When you construct a JWE, you have to provide the public key of the JWE’s recipient. During initialization, a shared content encryption key is created. The provided payload is then encrypted using this key, yielding the ciphertext. The shared content encryption key is then encrypted using the recipient’s public key and included in the JWE.
The recipient can use his matching private key to decrypt the encrypted content encryption key. Using this decrypted key he can decrypt the ciphertext and read the JWE payload.
Click here for a visualization of the JWE encryption and decryption flow.
Provided By You Computed Internally JWE Recipient
+-------------+ +---------------+ +-------------+
| | | | | |
| Payload +------------------^-------------------------> Ciphertext +---------------------------^----+ Payload |
| | | | | | | |
+-------------+ | +---------------+ | +-------------+
encrypts | decrypts |
| |
| |
+-------------+--------------+ +----------------------------+ +-------------+--------------+
| | | | | |
| Shared +--^--> Encrypted +--^--> Shared |
| Content Encryption Key | | | Content Encryption Key | | | Content Encryption Key |
| | | | | | | |
+----------------------------+ | +----------------------------+ | +----------------------------+
| |
| |
| |
+----------------+ encrypts | | decrypts +-----------------+
| | | | | |
| Public Key +----------------------------------+ +------------+ Private Key |
| | | |
+----------------+ +-----------------+
Don’t worry though, most of this logic and the associated values are constructed under the hood. You’ll not have to worry about them too much. Nonetheless, they are described in more detail in the following sections.
The JWE Header is a JSON object specifying the encryption algorithms applied to the payload and the content encryption key. Optionally it can contain additional properties of the JWE.
The following header specifies two algorithms. The JWE’s payload is encrypted using the AES_256_CBC_HMAC_SHA_512
algorithm, yielding the ciphertext. The shared key, with which this payload encryption is performed is encrypted using the RSAES-PKCS1-v1_5
algorithm and included in the JWE.
{ "alg": "RSA1_5", "enc": "A256CBC-HS512" }
A detailed list describing possible header parameters can be found here.
The payload is encrypted using a shared content encryption key that is randomly generated when initializing a JWE. The recipient of the JWE has to know this key so that he or she can decrypt the ciphertext. This means that this content encryption key has to be encrypted as well and included in the JWE.
The public key used to encrypt this shared content encryption key has to be provided by you. The recipient can use the matching private key to decrypt the content encryption key and subsequently the ciphertext, yielding the payload.
If the content encryption algorithm uses an initialization vector, it’s value is included in the JWE. If it does not use an initialization vector, the value is empty.
An additional value that is integrity protected by the authenticated encryption operation. Since JOSESwift implements compact serialization, the JWE header is used as additional authenticated data. This means that the header is not encrypted but integrity protected.
The ciphertext resulting from the authenticated encryption of the plaintext and the additional authenticated data.
The output of the authenticated encryption that ensures the integrity of the ciphertext and the additional authenticated data. If the encryption algorithm does not use an authentication tag, the value is empty.
JOSESwift implements compact serialization for JWE. Compact serialization represents a JWE as the following concatenation:
base64url(header) + "." +
base64url(encrypted content encryption key) + "." +
base64url(initialization vector) + "." +
base64url(ciphertext) + "." +
base64url(authentication tag) + "."
Note that the additional authenticated data is not explicitly included in the compact serialization. This is because the header is used as additional authenticated data.
Given the following header and payload:
// Header
{ "alg": "RSA1_5", "enc": "A256CBC-HS512" }
// Payload
"Trumpets of Mexico 🏜"
We get the following values, encoded as base64url strings:
// base64url(header)
"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0"
// base64url(encrypted content encryption key)
"b5Zbb...NglvQ"
// base64url(initialization vector)
"3vgaWb4EQbk_W1PHiBZBlg"
// base64url(ciphertext)
"5Hjsko9pPFpmkaNDLBYj9fUZp52FGVIpJo-jdAut3wk"
// base64url(authentication tag)
"hZXvBYxZEV4x7ACGhx3ky5pzNuf1X6UsmI3iEcYjHmM"
Which yields the following compact serialization (with line breaks added for readability):
"""
eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.
b5Zbb...NglvQ.
3vgaWb4EQbk_W1PHiBZBlg.
5Hjsko9pPFpmkaNDLBYj9fUZp52FGVIpJo-jdAut3wk.
hZXvBYxZEV4x7ACGhx3ky5pzNuf1X6UsmI3iEcYjHmM
"""
This section describes how the above concepts are implemented and used in JOSESwift.
A JWE
in JOSESwift is an immutable struct
:
struct JWE {
let header: JWEHeader
let encryptedKey: Data
let initializationVector: Data
let ciphertext: Data
let authenticationTag: Data
}
In JOSESwift you can construct a new JWE from some arbitrary payload or parse an existing compact serialized JWE to decrypt its payload.
You need three parts to construct a new JWE
instance in JOSESwift.
- A
JWEHeader
instance - A
Payload
instance - An
Encrypter
instance which computes the needed encryptions and its associated values.
These three parts are described in more detail in the following sections.
A JWEHeader
is a struct, holding a dictionary of parameters:
struct JWEHeader {
let parameters: [String: Any]
}
For convenient use, a JWEHeader
has an initializer that lets you provide the value of its two encryption algorithms:
init(algorithm: AsymmetricKeyAlgorithm, encryptionAlgorithm: SymmetricKeyAlgorithm)
The algorithm
value describes the algorithm used to encrypt the shared content encryption key. The encryptionAlgorithm
value describes the algorithm used to encrypt the payload using that shared content encryption key.
To instantiate a JWEHeader
you specify the algorithms that it should carry:
let header = JWEHeader(algorithm: .RSA1_5, encryptionAlgorithm: .A256CBCHS512)
Optionally you can set addtitional parameters:
header.kid = "2018-10-08"
header.typ = "JWE"
Payload
has an initializer that lets you specify the data that your JWE should encrypt:
init(_ payload: Data)
To instantiate Payload
you provide it with the data that it should carry:
let message = "Trumpets of Mexico 🏜"
let data = message.data(using: .utf8)!
let payload = Payload(data)
An Encrypter
handles all cryptographic functionality needed to compute a JWE. It then passes all cryptographic computations to the respective libraries providing the cryptographic primitives:
- The generation of the shared content encryption key
- The encryption of the shared content encryption key
- The encryption of the payload, yielding the ciphertext, initialization vector, and authentication tag
Encrypter
provides an initializer which lets you specify the desired encryption algorithms and the public key of the receiver:
init<KeyType>(keyEncryptionAlgorithm: AsymmetricKeyAlgorithm, keyEncryptionKey kek: KeyType, contentEncyptionAlgorithm: SymmetricKeyAlgorithm)
Note that the KeyType
depends on the underlying crypto implementation. On iOS it’s SecKey
per default. The initializer will return nil
if you specify the wrong key type.
Make sure to check that you get a valid Encrypter
instance after calling the initializer:
let publicKey: SecKey = /* ... */
guard let encrypter = Encrypter(keyEncryptionAlgorithm: .RSA1_5, keyEncryptionKey: publicKey, contentEncyptionAlgorithm: .A256CBCHS512) else {
// Wrong key type.
}
// You have a valid encrypter.
As a framework user, you don’t need to worry about calling the encrypter’s encrypt
function. It happens automatically during the initialization of a JWE.
Once you have created the header, payload, and encrypter, you can construct a JWE using the following initializer:
init<KeyType>(header: JWEHeader, payload: Payload, encrypter: Encrypter<KeyType>) throws
Make sure to check if any errors occur during the initialization:
do {
let jwe = try JWE(header: header, payload: payload, encrypter: signer)
} catch {
// Encryption went wrong.
}
Now that you have an initialized JWE, you can get its compact serialization using the following properties:
jwe.compactSerializedString
// or
jwe.compactSerializedData
If you have an existing JWE in compact serialization, you can construct a full JWE instance from it. You can then decrypt the ciphertext to extract the payload it carries.
Use one of the following initializers to construct a JWE from a compact serialization:
init(compactSerialization: String) throws
// or
init(compactSerialization: Data) throws
Make sure to check if any errors occur during the initialization:
do {
let jwe = try JWE(compactSerialization: serialization)
} catch {
// Something went wrong.
}
Once you have a JWE instance you can decrypt its ciphertext to access the payload. To decrypt the ciphertext, use the following function:
func decrypt<KeyType>(with kdk: KeyType) throws -> Payload
Note that the KeyType
depends on the underlying crypto implementation. On iOS it’s SecKey
per default. The decryption will fail if you specify the wrong key type.
let privateKey: SecKey = /* ... */
do {
let payload = try jwe.decrypt(with: privateKey)
} catch {
// Something went wrong.
}
Once you have decrypted the ciphertext to a Payload
instance, use it however you like. Note that the decrypted payload is not stored in the JWE. You need to either store it yourself or decrypt the JWE every time you want to access it.
let data = payload.data()
let message = String(data: data, encoding: .utf8)!
print(message) // "Trumpets of Mexico 🏜"