class SecurityIO extends Object
A class to provide cryptographic features to Jervis such as RSA encryption and base64 encoding.
To run this example, clone Jervis and execute ./gradlew console to bring up a Groovy Console with the classpath set up.
import net.gleske.jervis.tools.SecurityIO
if(!(new File('/tmp/id_rsa').exists())) {
'openssl genrsa -out /tmp/id_rsa 4096'.execute().waitFor()
'openssl rsa -in /tmp/id_rsa -pubout -outform pem -out /tmp/id_rsa.pub'.execute().waitFor()
}
def security = new SecurityIO(new File("/tmp/id_rsa").text)
println "Key size: ${security.rsa_keysize}"
def s = security.rsaEncrypt('hello friend')
println 'Length of encrypted output: ' + s.length()
println 'Encrypted string:'
println s
println 'Decrypted string:'
println security.rsaDecrypt(s)
new File('/tmp/id_rsa').delete()
new File('/tmp/id_rsa.pub').delete()
| Type | Name and description |
|---|---|
static Integer |
DEFAULT_AES_ITERATIONSThe default number of iterations of SHA-256 hashing of the AES IV when AES encryption or decryption is performed. |
KeyPair |
key_pairA decoded RSA key pair used for encryption and decryption, and signing. |
| Constructor and description |
|---|
SecurityIO
()Instantiates an unconfigured instance of this class. |
SecurityIO
(String private_key_pem)Instantiates the class and configures a private key for decryption. |
| Type Params | Return Type | Name and description |
|---|---|---|
|
static def |
avoidTimingAttack(Integer milliseconds, Closure body)This is a constant-time function intended to wrap security-sensitive code. |
|
static byte[] |
decodeBase64Bytes(String content)Decode a base64 String into Bytes. |
|
static String |
decodeBase64String(String content)Decode a base64 String. |
|
static byte[] |
decodeBase64UrlBytes(String content)Decode a URL safe base64 String into Bytes. |
|
String |
decodeBase64UrlString(String content)Decode a URL safe base64 String into a String. |
|
static String |
decryptWithAES256(byte[] secret, byte[] iv, byte[] data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Decrypt ciphertext using AES-256 CBC mode with PKCS5 padding. |
|
static String |
decryptWithAES256(String passphrase, String data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Decrypts ciphertext with AES-256 using a passphrase. |
|
static String |
decryptWithAES256Base64(String secret, String iv, String data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Takes Base64 encoded secret and IV and decrypts the provided Base64 encoded String data. |
|
static String |
decryptWithAES256GCM(byte[] secret, byte[] data)Decrypt ciphertext using AES-256 GCM mode with authenticated decryption. |
|
static String |
decryptWithAES256GCMBase64(String secret, String data)Takes Base64 encoded secret and decrypts the provided Base64 encoded ciphertext using AES-256-GCM authenticated decryption. |
|
static String |
decryptWithPassphraseGCM(String passphrase, String data)Decrypts ciphertext with AES-256-GCM using a passphrase. |
|
static String |
encodeBase64(String content)Encode a String into a base64 String. |
|
static String |
encodeBase64(byte[] content)Encode raw Bytes into a base64 String. |
|
static String |
encodeBase64Url(String content)Encode raw Bytes into a URL safe base64 String. |
|
static String |
encodeBase64Url(byte[] content)Encode raw Bytes into a URL safe base64 String. |
|
static byte[] |
encryptWithAES256(byte[] secret, byte[] iv, String data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Encrypt plaintext using AES-256 CBC mode with PKCS5 padding. |
|
static String |
encryptWithAES256(String passphrase, String data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Encrypts String data with AES-256 using a passphrase. |
|
static String |
encryptWithAES256Base64(String secret, String iv, String data, Integer hash_iterations = DEFAULT_AES_ITERATIONS)Takes Base64 encoded secret and IV and encrypts the provided String data. |
|
static byte[] |
encryptWithAES256GCM(byte[] secret, String data)Encrypt plaintext using AES-256 GCM mode with authenticated encryption. |
|
static String |
encryptWithAES256GCMBase64(String secret, String data)Takes Base64 encoded secret and encrypts the provided String data using AES-256-GCM authenticated encryption. |
|
static String |
encryptWithPassphraseGCM(String passphrase, String data)Encrypts String data with AES-256-GCM using a passphrase. |
|
String |
getGitHubJWT(String github_app_id, Integer expiration = 10, Integer drift = 30)Get a JSON Web Token (JWT) meant for use with GitHub App Authentication. |
|
Integer |
getRsa_keysize()Returns the calculated RSA private key size in bits calculated from the key_pair. |
|
static Boolean |
isBase64(String s)Check if String is valid base64 according to RFC 4648. |
|
static Boolean |
isSecureField(def field)Checks to see if a field in the Jervis YAML is a secure field. |
|
static byte[] |
padForAES256(byte[] input)Repeats the input bytes until enough bytes are provided for AES-256. |
|
static byte[] |
randomBytes(int size)Returns a random of bytes. |
|
static String |
randomBytesBase64(int size)Base64 encoded random bytes. |
|
String |
rsaDecrypt(String ciphertext)Uses RSA asymmetric encryption to decrypt a ciphertext String and outputs plain text. |
|
byte[] |
rsaDecryptBytes(byte[] cipherbytes)Uses RSA asymmetric encryption to decrypt enciphered bytes and returns plain bytes. |
|
byte[] |
rsaDecryptBytesOaep(byte[] cipherbytes)Uses RSA asymmetric encryption with OAEP padding to decrypt enciphered bytes. |
|
String |
rsaDecryptOaep(String ciphertext)Uses RSA asymmetric encryption with OAEP padding to decrypt a ciphertext String. |
|
String |
rsaEncrypt(String plaintext)Uses RSA asymmetric encryption to encrypt a plain text String and outputs ciphertext. |
|
byte[] |
rsaEncryptBytes(byte[] plainbytes)Uses RSA asymmetric encryption to encrypt a plain bytes and outputs enciphered bytes. |
|
byte[] |
rsaEncryptBytesOaep(byte[] plainbytes)Uses RSA asymmetric encryption with OAEP padding to encrypt plain bytes. |
|
String |
rsaEncryptOaep(String plaintext)Uses RSA asymmetric encryption with OAEP padding to encrypt a plain text String. |
|
void |
setKey_pair(String pem)Sets key_pair by decoding the String. |
|
static String |
sha256Sum(String input)Calculates SHA-256 sum from a String. |
|
static String |
sha256Sum(byte[] input)Calculates SHA-256 sum from a byte-array. |
|
String |
signRS256Base64Url(String data)Creates a URL safe base64 string of a signature using algorithm RS256. |
|
Boolean |
verifyGitHubJWTPayload(String github_jwt, Integer drift = 30)Verify a GitHub JWT is not expired by checking the signature and ensuring current time falls within issued at and expiration. |
|
Boolean |
verifyJsonWebToken(String github_jwt)Use the loaded public key to verify the provided JSON web token (JWT). |
|
Boolean |
verifyRS256Base64Url(String signature, String data)Verify data signed by RS256 Base64 URL encoded signature. |
The default number of iterations of SHA-256 hashing of the AES IV when AES encryption or decryption is performed. Default: 5000 iterations.
A decoded RSA key pair used for encryption and decryption, and signing.
Instantiates an unconfigured instance of this class. Call setKey_pair(java.lang.String) to properly use this class.
Instantiates the class and configures a private key for decryption. Automatically calls setKey_pair(java.lang.String) as part of instantiating.
private_key_pem - The contents of an X.509 PEM encoded RSA private key.This is a constant-time function intended to wrap security-sensitive code. This forces code to always take a certain amount of time regardless of input in order to have a constant-time result to avoid timing based attacks.
If milliseconds is a negative number, e.g. -200, then the delay will be randomized between 0 and milliseconds but not more than milliseconds.
Please Note: due to how time works on computers sometimes the time can be 1 or 2 ms over the time you specify. If you have an absolute maximum, then you should account for this in the value you pass. This function can't guarantee absolute maximum.
Please Note: this function provides fixed maximum delays. If code being called goes over this fixed delay, then timing attacks are still possible on your code. You should account for this with defensive programming such as validating inputs on security sensitive code before calling algorithms.
Please note: randomized delay can have a limit. If an attacker has infinite time and unlimited samples, then statistically randomness goes away. This function makes it harder to calculate timing attacks; other protections should be in place on your code endpoints such as request limits within a given time frame.
Please note: These notes are known as defense in depth. Have multiple controls around securing your code in case one control fails. Knowing the weakness of different controls enables you to better secure it with additional layers of security.
To run this example, clone Jervis and execute ./gradlew console to bring up a Groovy Console with the classpath set up.
import static net.gleske.jervis.tools.SecurityIO.avoidTimingAttack
import java.time.Instant
Integer mysecret = 0
Long before = Instant.now().toEpochMilli()
// Force code to always take 200ms
avoidTimingAttack(200) {
mysecret = 1+1
}
assert mysecret == 2
println("Time taken (milliseconds): ${Instant.now().toEpochMilli() - before}ms")
before = Instant.now().toEpochMilli()
// Force code to randomly delay up to 200ms
avoidTimingAttack(-200) {
mysecret = mysecret + 2
}
assert mysecret == 4
println("Time taken (milliseconds): ${Instant.now().toEpochMilli() - before}ms")
before = Instant.now().toEpochMilli()
// Set an implicit value. Minimum execution time is 100ms random between 100-200ms.
mysecret = avoidTimingAttack(100) {
avoidTimingAttack(-200) {
mysecret + mysecret
}
}
assert mysecret == 8
println("Time taken (milliseconds): ${Instant.now().toEpochMilli() - before}ms")
milliseconds - The number of milliseconds a section of code must
minimally take. If milliseconds is negative,
then randomly delay up to the value. In both cases,
the maximum delay is fixed at the absolute value of
milliseconds.body - A closure of code to execute. The code will execute as fast
as it can and this function will enforce a constant time.Decode a base64 String into Bytes.
content - Base64 encoded String.Decode a base64 String.
content - Base64 encoded String.Decode a URL safe base64 String into Bytes.
content - URL safe base64 encoded String.Decode a URL safe base64 String into a String.
content - A URL safe base64 encoded String.Decrypt ciphertext using AES-256 CBC mode with PKCS5 padding. The IV is hashed with multiple iterations of SHA-256.
secret - Secret key for decrypting. If byte-count is less than 32
(256-bits), then bytes are repeated until 256 bits are available.iv - Initialization vector (IV) used to initialize the cipher.data - Data to be encrypted with AES-256.hash_iterations - The IV is hashed with SHA-256. For each iteration
the iv is combined with the previous
iteration result of the hashing. The resulting
bytes are fed into PBKDF2WithHmacSHA256
resulting in a new initialization vector. If set
to 0, then hashing is skipped and the
original iv is used for AES cipher
initialization.Decrypts ciphertext with AES-256 using a passphrase. See the encrypt method for more details on both algorithms and usage.
passphrase - A passphrase used to decrypt AES-256 ciphertext. See
encrypt method for more details.data - Base64 encoded ciphertext to be decrypted.hash_iterations - The number of iterations the AES-256 initialization
vector (IV) is hashed with SHA-256. The minimum
iterations allowed is 1 for password-based
encryption.Takes Base64 encoded secret and IV and decrypts the provided Base64 encoded String data.
secret - Base64 encoded binary secret.iv - Base64 encoded binary initialization vector (IV).data - Base64 encoded encrypted bytes to decrypt.Decrypt ciphertext using AES-256 GCM mode with authenticated decryption. Expects the nonce (12 bytes) to be prepended to the ciphertext. GCM mode provides both confidentiality and authenticity verification.
secret - Secret key for decrypting. If byte-count is less than 32
(256-bits), then bytes are repeated until 256 bits are available.data - Data to be decrypted with AES-256-GCM. Must have 12-byte nonce prepended.Takes Base64 encoded secret and decrypts the provided Base64 encoded ciphertext using AES-256-GCM authenticated decryption.
secret - Base64 encoded binary secret.data - Base64 encoded encrypted bytes to decrypt (with nonce prepended).Decrypts ciphertext with AES-256-GCM using a passphrase. Expects the format: salt (32 bytes) || nonce (12 bytes) || ciphertext || auth-tag Provides authenticated decryption that verifies integrity.
passphrase - A passphrase used to decrypt AES-256-GCM ciphertext.data - Base64 encoded ciphertext to be decrypted (with prepended salt and nonce).Encode a String into a base64 String.
content - A plain String.Encode raw Bytes into a base64 String.
content - Base64 encoded String.Encode raw Bytes into a URL safe base64 String.
content - A plain String.Encode raw Bytes into a URL safe base64 String.
content - Raw Bytes.Encrypt plaintext using AES-256 CBC mode with PKCS5 padding. The IV is hashed with multiple iterations of SHA-256.
secret - Secret key for decrypting. If byte-count is less than 32
(256-bits), then bytes are repeated until 256 bits are available.iv - Initialization vector (IV) used to initialize the cipher.data - Data to be encrypted with AES-256.hash_iterations - The IV is hashed with SHA-256. For each iteration
the iv is combined with the previous
iteration result of the hashing. The resulting
bytes are fed into PBKDF2WithHmacSHA256
resulting in a new initialization vector. If set
to 0, then hashing is skipped and the
original iv is used for AES cipher
initialization.Encrypts String data with AES-256 using a passphrase.
import net.gleske.jervis.tools.SecurityIO
import java.time.Instant
Long time(Closure c) {
Long before = Instant.now().epochSecond
c()
Long after = Instant.now().epochSecond
after - before
}
String encrypted
Long timing1 = time {
encrypted = SecurityIO.encryptWithAES256('correct horse battery staple', 'My secret data')
}
println("Encrypted text: ${encrypted}")
Long timing2 = time {
println("Decrypted text: ${SecurityIO.decryptWithAES256('correct horse battery staple', encrypted)}")
}
println "Encrypt time: ${timing1} second(s)\nDecrypt time: ${timing2} second(s)"
passphrase - A passphrase used to generate AES keys for encryption.
The passphrase creates an AES key using
PBKDF2WithHmacSHA256, a salt created from
passphrase checksum, and variable PBKDF2 iterations
based on passphrase checksum. The PBKDF2 iterations
are between 100100 and 960000.data - Data to be encrypted.hash_iterations - The number of iterations the AES-256 initialization
vector (IV) is hashed with SHA-256. The minimum
iterations allowed is 1 for password-based
encryption.Takes Base64 encoded secret and IV and encrypts the provided String data.
secret - Base64 encoded binary secret.iv - Base64 encoded binary initialization vector (IV).data - A plain String to be encrypted (not Base64 encoded).Encrypt plaintext using AES-256 GCM mode with authenticated encryption. Uses a random 12-byte nonce which is prepended to the ciphertext. GCM mode provides both confidentiality and authenticity.
secret - Secret key for encrypting. If byte-count is less than 32
(256-bits), then bytes are repeated until 256 bits are available.data - Data to be encrypted with AES-256-GCM.Takes Base64 encoded secret and encrypts the provided String data using AES-256-GCM authenticated encryption.
secret - Base64 encoded binary secret.data - A plain String to be encrypted (not Base64 encoded).Encrypts String data with AES-256-GCM using a passphrase. Uses a random 32-byte salt which is prepended to the ciphertext. Provides authenticated encryption for both confidentiality and integrity.
import net.gleske.jervis.tools.SecurityIO
import java.time.Instant
Long time(Closure c) {
Long before = Instant.now().epochSecond
c()
Long after = Instant.now().epochSecond
after - before
}
String encrypted
Long timing1 = time {
encrypted = SecurityIO.encryptWithPassphraseGCM('correct horse battery staple', 'My secret data')
}
println("Encrypted text: ${encrypted}")
Long timing2 = time {
println("Decrypted text: ${SecurityIO.decryptWithPassphraseGCM('correct horse battery staple', encrypted)}")
}
println "Encrypt time: ${timing1} second(s)\nDecrypt time: ${timing2} second(s)"
passphrase - A passphrase used to generate AES keys for encryption.
The passphrase creates an AES key using
PBKDF2WithHmacSHA256, a random 32-byte salt,
and variable PBKDF2 iterations based on passphrase checksum.
The PBKDF2 iterations are between 100100 and 960000.
The random salt is prepended to the ciphertext.data - Data to be encrypted.Get a JSON Web Token (JWT) meant for use with GitHub App Authentication. This assumes the SecurityIO class was loaded with an RSA private key provided by GitHub App Authentication setup.
import net.gleske.jervis.tools.SecurityIO
if(!(new File('/tmp/id_rsa').exists())) {
'openssl genrsa -out /tmp/id_rsa 4096'.execute().waitFor()
'openssl rsa -in /tmp/id_rsa -pubout -outform pem -out /tmp/id_rsa.pub'.execute().waitFor()
}
def security = new SecurityIO(new File("/tmp/id_rsa").text)
// use a fake App ID of 1234
String jwt = security.getGitHubJWT('1234')
if(security.verifyGitHubJWTPayload(jwt)) {
println('JWT is currently valid.')
} else {
println('JWT is invalid or expired.')
}
// Issue a 1 minute duration JWT, using clock drift 2 minutes in the past so that it is expired
jwt = security.getGitHubJWT('1234', 1, 120)
if(security.verifyGitHubJWTPayload(jwt)) {
println('JWT is currently valid.')
} else {
println('JWT is invalid or expired.')
}
github_app_id - The GitHub App ID available when generating a GitHub App.expiration - The duration of the JWT in minutes before the JWT
expires. The maximum value supported by GitHub is
10 minutes. Minimum expiration is 1
minute. Default: 10 minutes.drift - Number of seconds in the past used as the starting
point of the JWT. This is to account for clock
drift between two remote systems as a conservative
measure. If the drift is set for 30 seconds that
means the issues JWT will expire in 9 minutes, 30
seconds in the future (10 minutes total if you
include 30 seconds for clock drift).
Default: 30 seconds.Returns the calculated RSA private key size in bits calculated from the key_pair. e.g. a 4096-bit RSA key will return 4096.
import net.gleske.jervis.tools.SecurityIO
def security = new SecurityIO(new File("/tmp/id_rsa").text)
println "Key size: ${security.rsa_keysize}"
Check if String is valid base64 according to RFC 4648.
s - Check if String is similar to valid base64 encoding.Checks to see if a field in the Jervis YAML is a secure field. If it is then decryption should be attempted. This only detects of decryption is plausible.
property - A simple object that can take multiple types to check against.Repeats the input bytes until enough bytes are provided for AES-256.
input - Bytes to be repeated.Returns a random of bytes.
size - Number of random bytes to generate.Base64 encoded random bytes.
size - Number of random bytes to generate.Uses RSA asymmetric encryption to decrypt a ciphertext String and outputs plain text. For third party reference, this is essentially executing the following commands in a terminal.
echo 'ciphertext' | openssl enc -base64 -A -d | openssl rsautl -decrypt -inkey /tmp/id_rsa
ciphertext - A Base64 encoded ciphertext String to be decrypted.Uses RSA asymmetric encryption to decrypt enciphered bytes and returns plain bytes.
cipherbytes - Encrypted bytes.Uses RSA asymmetric encryption with OAEP padding to decrypt enciphered bytes. OAEP padding is recommended over PKCS1 padding to prevent Bleichenbacher attacks.
cipherbytes - Encrypted bytes.Uses RSA asymmetric encryption with OAEP padding to decrypt a ciphertext String. OAEP padding is recommended over PKCS1 padding to prevent Bleichenbacher attacks.
ciphertext - A Base64 encoded ciphertext String to be decrypted.Uses RSA asymmetric encryption to encrypt a plain text String and outputs ciphertext. For third party reference, this is essentially executing the following commands in a terminal.
echo -n 'plaintext' | openssl rsautl -encrypt -inkey ./id_rsa.pub -pubin | openssl enc -base64 -A
plaintext - A plain text String to be encrypted.Uses RSA asymmetric encryption to encrypt a plain bytes and outputs enciphered bytes.
plainbytes - Plain bytes to be encrypted.Uses RSA asymmetric encryption with OAEP padding to encrypt plain bytes. OAEP padding is recommended over PKCS1 padding to prevent Bleichenbacher attacks.
plainbytes - Plain bytes to be encrypted.Uses RSA asymmetric encryption with OAEP padding to encrypt a plain text String. OAEP padding is recommended over PKCS1 padding to prevent Bleichenbacher attacks.
plaintext - A plain text String to be encrypted.Sets key_pair by decoding the String.
pem - An X.509 PEM encoded RSA private key.Calculates SHA-256 sum from a String.
input - A String to calculate a SHA-256 digest.Calculates SHA-256 sum from a byte-array.
input - A String to calculate a SHA-256 digest.Creates a URL safe base64 string of a signature using algorithm RS256. This data signature is meant for use in JSON Web Tokens.
data - Any data meant to be signed with RS256 algorithm.Verify a GitHub JWT is not expired by checking the signature and ensuring current time falls within issued at and expiration. This does both signature checking and decoding the payload to check for validity. See also GitHub App Authentication.
github_jwt - A JWT meant for use in GitHub App Auth.drift - Add seconds into the future in order to account for
clock drift. If 30 seconds are added that
means this will assume a token expires 30 seconds
before it really expires. If you issue a token with a
30 second drift and verify the token with a 30 second
drift then the maximum duration for a token is
9 minutes.
Default: 30 seconds.Use the loaded public key to verify the provided JSON web token (JWT).
jwt - A JSON Web Token meant for authentication.Verify data signed by RS256 Base64 URL encoded signature.
signature - A Base64 URL encoded signature of RS256 algorithm.data - The data in which the signature should verify.Jervis API documentation.