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_ITERATIONS The default number of iterations of SHA-256 hashing of the AES IV when AES encryption or decryption is performed. |
KeyPair |
key_pair A 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 |
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. |
|
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 |
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. |
|
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. |
|
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.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).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}"
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 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.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.