https://github.com/dinochiesa/apigee-custompolicy-rsacrypto
This is a custom Apigee policy, implemented in Java, that performs RSA Encryption and Decryption of data or message payloads, or RSA signing of data or message payloads, or verification of such signatures.
https://github.com/dinochiesa/apigee-custompolicy-rsacrypto
apigee cryptography rsa
Last synced: 4 months ago
JSON representation
This is a custom Apigee policy, implemented in Java, that performs RSA Encryption and Decryption of data or message payloads, or RSA signing of data or message payloads, or verification of such signatures.
- Host: GitHub
- URL: https://github.com/dinochiesa/apigee-custompolicy-rsacrypto
- Owner: DinoChiesa
- License: apache-2.0
- Created: 2019-11-02T00:56:58.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2025-01-21T23:44:13.000Z (9 months ago)
- Last Synced: 2025-05-26T20:28:05.202Z (4 months ago)
- Topics: apigee, cryptography, rsa
- Language: Java
- Homepage:
- Size: 6.12 MB
- Stars: 2
- Watchers: 2
- Forks: 4
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# RSA Crypto callout
This directory contains the Java source code for a Java callout for Apigee that
performs RSA Encryption and Decryption of data or message payloads, or RSA
signing of data or message payloads, or verification of such signatures.Specifically it can perform:
* RSA encryption or decryption with PKCS1 padding.
* RSA encryption or decryption with OAEP padding, using SHA-256 as the primary and MGF1 hash functions. (For more on OAEP, see [this](https://stackoverflow.com/a/49484492/48082).)
* RSA signing and verification with the RSASSA-PKCS1-v1_5 scheme or the RSASSA-PSS scheme. With either scheme, you can specify the primary hash function. With PSS, you can also specify hash used within MGF1. These all default to SHA-256. For more on these schemes see [RFC 3447](https://www.rfc-editor.org/rfc/rfc3447.html#section-8).There are two callout classes:
* com.google.apigee.callouts.RsaCrypto
* com.google.apigee.callouts.RsaSignatureThe former does encryption and decryption. The latter does signing and verification.
## Background on Encryption
There are payload size limits when you encrypt using RSA keys. In more detail,
RSA, as defined by PKCS#1, can be used to encrypt messages of limited size. In
fact, an encrypted message always has the same size as the modulus; for a
1024-bit RSA key, this means 128 bytes. for a 2048-bit RSA key, 256 bytes. The
encryption process includes some padding. With the commonly used "v1.5 padding"
and a 2048-bit RSA key, the maximum size of data that can be encrypted with RSA
is 245 bytes. ([cite](https://security.stackexchange.com/a/33445/81523)) For
OAEP padding and the same 2048-bit key, the maximum size is 214 bytes.This may seem to be a severe limitation. But, in practice people avoid this
limitation by using hybrid cryptosystems: use RSA encryption to encrypt a
symmetric key, which is small, and fits under the limit for RSA crypto. Then use
that symmetric key to AES-encrypt a larger message. There are two keys - the
first key encrypts the second key; let's call the first key the
key-encrypting-key. The second key encrypts the content, let's call it the
content-encrypting-key (CEK).Then the encrypting party can send the encrypted payload along with the
encrypted content-encrypting-key. The decrypting party uses the private RSA key
to decrypt the CEK, then uses the decrypted CEK to decrypt the payload.This common pattern is known as a
["hybrid cryptosystem"](https://en.wikipedia.org/wiki/Hybrid_cryptosystem). This
model is used in TLS, encrypted JWT, PGP, S/MIME, and many other security
protocols.The general pattern for encryption is:
1. generate a random AES key,
2. encrypt the plaintext with that key using AES with some specific mode, IV, etc.
3. encrypt the AES key with RSA
4. concatenate those two ciphertexts in some way in the output stream, and transmit
that to the trusted receiver. (You probably also need to transmit the IV)## This callout does not perform hybrid crypto
This callout does not perform hybrid cryptography.
This callout does only RSA crypto. It can do two things:- encrypt a small payload with an RSA public key; this corresponds to the 3rd step in the above.
- decrypt a small payload with an RSA private key. This would be the converse of the above.When encrypting, the callout policy can also generate a random key; this
corresponds to the 1st step in the above.If you want to implement the hybrid cryptosystem, then you'll want to couple
this callout with something that does AES crypto. For that you may want to use
the [AES Crypto callout](https://github.com/DinoChiesa/Apigee-CustomPolicy-AesCrypto).By the way, this pattern is what underlies the "encrypted JWT" standard when
using asymmetric keys.## License
This code is Copyright (c) 2017-2025 Google LLC, and is released under the
Apache Source License v2.0. For information see the [LICENSE](LICENSE) file.## Disclaimer
This example is not an official Google product, nor is it part of an official Google product.
## Using the Custom Policy
You do not need to build the Jar in order to use the custom policy. You do need
to package the jar and its dependencies into your API proxy.When you use the policy to encrypt data, the resulting cipher-text can be
decrypted by other systems. Likewise, the policy can decrypt cipher-text
obtained from other systems. To do that, the encrypting and decrypting systems
need to use a matched key pair (public and private), the same padding (either
PKCS1 or OAEP).The policy performs only RSA crypto.
## Policy Configuration
There are a variety of options, which you can select using Properties in the configuration. Examples follow, but here's a quick summary:
- the policy uses as its source, the message.content. If you wish to encrypt, decrypt, sign or verify something else, specify it with the source property
- When encrypting,
- the policy always uses ECB, which is sort of a misnomer since it's a single block encryption.
- when using OAEP padding, the policy uses SHA-256 and MGF1 as the hashes. You cannot override either of those. The MGF1 internally by default uses SHA-256, and you _can_ override that with the mgf1-hash property. Specify one of {SHA-1, SHA-256, SHA-384, SHA-512}.- When signing,
- when using the PKCS1 scheme, the policy uses the algorithm SHA-256 by default as the primary hash. You can override that with other values, ex SHA-1.
- when using the PSS scheme, the policy uses SHA-256 as the primary hash and SHA-256 as the hash for MGF1. These must be the same.- you can optionally encode (base64, base64url, base16) the output byte stream upon encryption or signing.
- When decrypting,
- you can optionally UTF-8 decode the output octet stream upon decryption.## Example: Signing with the PKCS v1.5 Scheme
```xml
sign
PKCS_V1.5
{my_private_key}
base64
sign
{my_private_key}
PSS
base64url
com.google.apigee.callouts.RsaSignature
java://apigee-callout-rsa-crypto-20250121.jar
```This policy works like the prior example, with these exceptions:
* The `scheme` property tells the policy to use the RSASSA-PSS signing scheme. There is no `primary-hash` or `mgf1-hash` property, so those default to `SHA-256`.
To verify the resulting signature, either within Apigee with this policy, or
using some other system, the verifier needs to use the corresponding public
key, and PSS scheme, with SHA-256 as the primary hash and the mgf1 function.## Example: Verifying with PSS Padding
```xml
verify
{my_public_key}
PSS
base64url
request.header.signature
com.google.apigee.callouts.RsaSignature
java://apigee-callout-rsa-crypto-20250121.jar
```This policy verifies a base64url-encoded signature using the PSS scheme, using
SHA-256 for both the primary and MGF1 hashes.## Example: Basic Encryption with Numerous Defaults
```xml
encrypt
{my_public_key}
base64
com.google.apigee.callouts.RsaCrypto
java://apigee-callout-rsa-crypto-20250121.jar
```Here's what will happen with this policy configuration:
* the `action` is encrypt, and the class is RsaCrypto, so the policy will encrypt.
* No `source` property is specified, therefore this policy will encrypt the message.content.
* When using the policy to encrypt, you must specify a public key. With the above configuration, the policy will deserialize the public key from the PEM string contained in the variable `my_public_key`.
* There is no `padding` property specified, so the policy will use PKCS1 padding.
* No `output` property is present, so the policy will encode the resulting ciphertext via base64, and store it into a variable named crypto_output (the default).To decrypt the resulting ciphertext, either within Apigee with this policy, or
using some other system, the decryptor needs to use the corresponding private
key, and the same padding.## Example: Generate an AES key and Encrypt it
```xml
encrypt
{my_public_key}
true
base64
com.google.apigee.callouts.RsaCrypto
java://apigee-callout-rsa-crypto-20250121.jar
```Here's what will happen with this policy configuration:
* the `action` is encrypt, so the policy will encrypt
* the `generate-key` property is true, so
Because `generate-key` is true, the policy will generate a 128-bit random key and use that as the "source", or the thing to encrypt. The policy will ignore a `source` property when `generate-key` is true.
* The policy will deserialize the public key from the PEM string contained in the variable `my_public_key`
* There is no `padding` specified, so PKCS1Padding is used.
* The policy stores the outputs - the generated key in cleartext, and the ciphertext for that
key (in other words, the encrypted version) - into context variables named `crypto_output_key` and `crypto_output`, encoded via base64.The proxy can subsequently use the encoded AES key to encrypt something via AES. The
caller can then send the ciphertext output (the encrypted key) of this policy,
along with the ciphertext of the AES encryption step, to a receiver. The
receiver can decrypt the encrypted AES key using the RSA private key, and the
same padding. Then the receiver can decrypt the AES-encrypted ciphertext with
the recovered AES key.### Example: Basic Decryption
```xml
decrypt
base64
{private.my_private_key}
true
com.google.apigee.callouts.RsaCrypto
java://apigee-callout-rsa-crypto-20250121.jar
```What will this policy configuration do?:
* the `action` is decrypt, so the policy will decrypt
* No `source` property is specified, therefore this policy will decrypt the message.content.
* Because there is a `decode-source` property, 'base64', the policy will base64-decode the message.content to derive the cipher text.
* There is no `padding` specified, so PKCS1 padding is used.
* The policy will attempt to decoded the cleartext bytes via UTF-8 to produce a plain string. Obviously, this will work only if the original clear text was a plain string encoded with UTF-8.### Full Properties List
The properties that are common to the RsaCrypto and RsaSigning classes are:
| Property | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| action | required. When using RsaCrypto, must be either "decrypt" or "encrypt". When using RsaSigning, must be either "sign" or "verify". |
| public-key | required when action = "encrypt" or "verify". a PEM string representing the public key. |
| private-key | required when action = "decrypt" or "sign". a PEM string representing the private key. |
| private-key-password | optional. a password to use with an encrypted private key. |
| source | optional. name of the context variable containing the data to encrypt or decrypt, or sign or verify. Do not surround in curly braces. Defaults to `message.content`. |
| decode-source | optional. one of "base16", "base64", or "base64url", to decode from a string to a octet stream. |
| debug | optional. true or false. If true, the policy emits extra context variables. Not for use in production. |
| encode-result | optional. One of {base16, base64, base64url}. The default is to not encode the result. |These are the properties available on the policy when using RsaCrypto (for encryption and decryption):
| Property | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| padding | optional. either PKCS1 or OAEP. OAEP implies OAEP with SHA-256 and MGF1 . |
| mgf1-hash | optional. The policy uses SHA256 for the inner hash used by MGF1. Specify one of {SHA1, SHA256, SHA384, SHA512} to override that. |
| output | optional. name of the variable in which to store the output. Defaults to crypto_output. |
| generate-key | optional. a boolean. Meaningful only when action = "encrypt". If true, the policy generates a random key of length 128 bits. |
| utf8-decode-result| optional. true or false. Applies only when action = decrypt. If true, the policy decodes the byte[] array into a UTF-8 string. |These are the properties available on the policy when using RsaSigning (for signing and verifying):
| Property | Description |
|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
| scheme | optional. either PSS or PKCS1_v1.5. |
| primary-hash | optional. For either scheme, the policy uses SHA-256 by default for the primary hash. You can specify {SHA-1, SHA-384, SHA-512} to override that. |
| mgf1-hash | optional. For PSS, the policy uses SHA-256 for the Mask generation function (MGF1). Specify one of {SHA-1, SHA-384, SHA-512} to override that. |
| output | optional. When signing, name of the variable in which to store the output. Defaults to signing_output. |
| signature-source | required when action = `verify`. This is the name of a variable containing the encoded signature. |
| decode-signature | optional when action = `verify`. One of {base16, base64, base64url}. Used to decode the signature. |
| generate-key | optional. a boolean. Meaningful only when action = "sign". If true, the policy generates a random RSA keypair, signs the payload with the private key, and emits the encoded form of the public and private keys into context variables. |## Detecting Success and Errors
The policy will return ABORT and set the context variable `crypto_error` if there has been any error at runtime. Your proxy bundles can check this variable in `FaultRules`.
Errors can result at runtime if:
* you do not specify an `action` property, or the `action` is neither `encrypt` nor `decrypt`, nor `sign` nor `verify`
* you pass an invalid string for the public key or private key
* you pass a padding option that is neither OAEP nor PKCS1 when encrypting
* you pass a scheme that is neither PSS nor PKCS_v1.5 when signing
* you specify `action` = decrypt or verify, and don't supply a `public-key`
* you specify `action` = encrypt or sign, and don't supply a `private-key`
* you use a `decode-*` parameter that is none of {base16, base64, base64url}
* some other configuration value is null or invalid
* you specify `action` = decrypt or verify, and the ciphertext is corrupted
* you specify `action` = encrypt, and the plaintext is more than 245 if you use PKCS1 padding, or more than 214 bytes if you use OAEP padding.## Building the Jar
You do not need to build the Jar in order to use the custom policy. The custom policy is
ready to use, with policy configuration. You do need
to package the jar and its dependencies into your API proxy.You need to re-build the jar only if you want
to modify the behavior of the custom policy. Before you do that, be sure you understand
all the configuration options - the policy may be usable for you without modification.If you do wish to build the jar, you will use [Apache Maven](https://maven.apache.org/) and Java. To build, you need:
- JDK 8 or JDK 11
- maven v3.9 at a minimumTo build on JDK 11, make sure you have a JDK11 bin on your path, and:
```
mvn clean package
```To build on JDK 8, make sure you have a JDK8 bin on your path, and:
```
mvn -f pom-java8.xml clean package
```The 'package' goal will copy the jar _and its dependencies_ to the resources/java directory for the
example proxy bundle. If you want to use this in your own API Proxy, you need
to copy this JAR and its dependencies into the appropriate API Proxy bundle. Or include the jar as an
environment-wide or organization-wide jar via the Apigee administrative API.## Build Dependencies
* Apigee expressions v1.0
* Apigee message-flow v1.0
* Bouncy Castle 1.70+These jars are specified in the pom.xml file.
The first two JARs are builtin to Apigee.
The BouncyCastle jars will be downloaded when you build, or you can download
them manually from maven.If you have other Java callouts that use BC, you could simply upload the
BouncyCastle jars as a resource, with either the organization or environment, to
satisfy the dependency.## Author
Dino Chiesa
godino@google.com## Bugs & Limitations
* When encrypting, does not allow parameterization of the hash function for RSA-OAEP. Always uses SHA-256.