Morning Musings

I'm not ready to wake up yet...

Cryptography Using OpenSSL

| Comments

PGP and GPG are commonly used to encrypt and sign messages for specified recipients, but OpenSSL is capable of performing the same cryptographic operations. The benefit is that more of the magic is exposed to the user, which can be useful for learning more about how cryptographic applications operate.

Below are three bash scripts that can perform the following:

  • Public / private key generation
  • Hybrid asymmetric encryption and signing
  • Hybrid asymmetric decryption and verification

These operations are a subset of the core functionality provided by GPG, and can be used to securely pass sensitive data between users. Unlike GPG, the user is responsible for managing trusted certificates.

Generate

The first script is called generate.sh. It will generate a new public certificate and private key when given a name and optional email address. Run it like so:

$ ./generate.sh 
Usage: generate.sh <"name"> [email]

$ ./generate.sh "Joe Ruether" jrruethe@gmail.com
Generate Usage

As you can see, a certificate and private key were generated, with the proper permissions set. Both files are stored in base64 ASCII for easily sharing or backing them up.

You can also view the human readable output of the certificate with:

openssl x509 -in Joe_Ruether.certificate -text -noout
Certificate Text

The idea here is that two users would generate their own certificates and private keys, then keep the private keys for themselves while sharing the certificates with each other. The sharing of certificates should be done in a way that you can prove the certificate belongs to who you think it does, since anyone can generate a certificate with any name and email.

The script:

(generate.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash

# generate.sh
# Copyright (C) 2016 Joe Ruether jrruethe@gmail.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Generates a certificate and private key
# generate.sh <"name"> [email]
# 1) (Required) Name of user
# 2) (Optional) Email of user

# Stop on any error
set -e

if [ "$#" -lt 1 ]; then
   echo "Usage: generate.sh <\"name\"> [email]"
   exit 1
fi

NAME=$1
EMAIL=$2

# Replace spaces with underscores
FILE=${NAME// /_}
KEY=${FILE}.secret
CERTIFICATE=${FILE}.certificate

# Create a certificate and key pair for the given name and email
echo -e "NA\nNA\nNA\nNA\nNA\n${NAME}\n${EMAIL}" | openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes -keyout ${KEY} -out ${CERTIFICATE}  > /dev/null 2>&1 

# Change permissions
chmod 400 ${KEY}
chmod 444 ${CERTIFICATE}

Encrypt

The next script performs file encryption to a specified recipient certificate. It can optionally sign the file with your private key. The order of operations is as follows:

  1. The file is first compressed
  2. A random keyfile is generated
  3. The compressed file is symmetrically encrypted with the random keyfile using AES256
  4. The random key is asymmetrically encrypted with the certificate using RSA
  5. (optional) The file is signed using the private key of the sender, and the signature is encrypted symmetrically using the random key
  6. All output files are bundled together into a tarball

Run it like so:

$ echo "This is a test" > test.txt
$ ./encrypt.sh 
Usage: encrypt.sh <file> <recipient_certificate> [sender_private_key] [sender_certificate]

$ ./encrypt.sh test.txt Alice.certificate
Encryption Successful

Optionally the file can be signed by also providing your private key:

$ ./encrypt.sh test.txt Alice.certificate Bob.secret
Encryption Successful

In this case, the file test.txt was encrypted to Alice and signed by Bob.

Here is the output:

Generating Certificates
Encryption Usage

The produced tarball can be safely shared over an insecure channel; only the intended recipient is able to decrypt it.

The tarball will always contain the recipient metadata extracted from the recipient’s public certificate. This is so it is easy to identify who is able to decrypt the file. Optionally, the sender can include their certificate metadata when signing a file, to make it easy to determine which certificate is needed to verify the signature. The metadata files contain nothing more than the sha1 fingerprints of the respective certificates:

Metadata

The script:

(encrypt.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/bin/bash

# encrypt.sh
# Copyright (C) 2016 Joe Ruether jrruethe@gmail.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Encrypts a file
# encrypt.sh <file> <recipient_certificate> [sender_private_key] [sender_certificate]
# 1) (Required) File to encrypt
# 2) (Required) Certificate of the recipient
# 3) (Optional) Private key of the sender
# 4) (Optional) Certificate of the sender

# Stop on any error
# set -e

# Declare an array of tasks to perform on exit
declare -a on_exit_items

# This function is run on exit
function on_exit()
{
    for i in "${on_exit_items[@]}"
    do
        eval $i
    done
}

# Add to the list of tasks to run on exit
function add_on_exit()
{
    local n=${#on_exit_items[*]}
    on_exit_items[$n]="$*"
    if [[ $n -eq 0 ]]; then
        trap on_exit EXIT
    fi
}

if [ "$#" -lt 2 ]; then
   echo "Usage: encrypt.sh <file> <recipient_certificate> [sender_private_key] [sender_certificate]"
   exit 1
fi

FILE=$1
RECIPIENT_CERTIFICATE=$2
SIGNING_KEY=$3
SENDER_CERTIFICATE=$4

COMPRESSED_FILE=${FILE}.bz2
ENCRYPTED_FILE=${COMPRESSED_FILE}.encrypted
SYMMETRIC_KEY=symmetric_key.bin
ENCRYPTED_KEY=${SYMMETRIC_KEY}.encrypted

RECIPIENT_PUBLIC_KEY=${RECIPIENT_CERTIFICATE//certificate/public}
RECIPIENT_METADATA=recipient.txt
SENDER_METADATA=sender.txt

SIGNATURE=${FILE}.signature
ENCRYPTED_SIGNATURE=${SIGNATURE}.encrypted
OUTPUT=${FILE}.tar

# Get the public key from the certificate
openssl x509 -in ${RECIPIENT_CERTIFICATE} -pubkey -noout > ${RECIPIENT_PUBLIC_KEY} 2>/dev/null
SUCCESS=$?
add_on_exit rm -f ${RECIPIENT_PUBLIC_KEY}

if [ ${SUCCESS} -ne 0 ]; then
   echo "Invalid recipient certificate"
   exit 1
fi

# Get the recipient fingerprint metadata
openssl x509 -in ${RECIPIENT_CERTIFICATE} -noout -fingerprint | awk -F "=" '{print $2}' > ${RECIPIENT_METADATA}
add_on_exit rm -f ${RECIPIENT_METADATA}

# Compress the file
bzip2 -9 -k ${FILE}
add_on_exit rm -f ${COMPRESSED_FILE}

# Generate a random key
openssl rand -base64 128 -out ${SYMMETRIC_KEY}
add_on_exit shred ${SYMMETRIC_KEY}
add_on_exit rm -f ${SYMMETRIC_KEY}

# Encrypt the file symmetrically using the random key
openssl enc -aes-256-cbc -salt -in ${COMPRESSED_FILE} -out ${ENCRYPTED_FILE} -pass file:${SYMMETRIC_KEY}
add_on_exit rm -f ${ENCRYPTED_FILE}

# Encrypt the symmetric key with the public key of the recipient
openssl rsautl -encrypt -inkey ${RECIPIENT_PUBLIC_KEY} -pubin -in ${SYMMETRIC_KEY} -out ${ENCRYPTED_KEY}
add_on_exit rm -f ${ENCRYPTED_KEY}

# If the file is being signed by the sender
if [ ! -z ${SIGNING_KEY} ]; then

   # Sign the file
   openssl dgst -sha256 -sign ${SIGNING_KEY} -out ${SIGNATURE} ${FILE} > /dev/null 2>&1
   SUCCESS=$?
   add_on_exit shred ${SIGNATURE}
   add_on_exit rm -f ${SIGNATURE}

   if [ ${SUCCESS} -ne 0 ]; then
      echo "Invalid sender private key"
      exit 1
   fi

   # Encrypt the signature symmetrically using the random key
   openssl enc -aes-256-cbc -salt -in ${SIGNATURE} -out ${ENCRYPTED_SIGNATURE} -pass file:${SYMMETRIC_KEY}
   add_on_exit rm -f ${ENCRYPTED_SIGNATURE}

else
   # Clear the variables for the tar command
   ENCRYPTED_SIGNATURE=
fi

# If a sender is being specified
if [ ! -z ${SENDER_CERTIFICATE} ]; then

   # Get the sender fingerprint metadata
   openssl x509 -in ${SENDER_CERTIFICATE} -noout -fingerprint 2>/dev/null | awk -F "=" '{print $2}' > ${SENDER_METADATA}
   SUCCESS=${PIPESTATUS[0]}
   add_on_exit rm -f ${SENDER_METADATA}

   if [ ${SUCCESS} -ne 0 ]; then
      echo "Invalid sender certificate"
      exit 1
   fi

else
   # Clear the variable for the tar command
   SENDER_METADATA=
fi

# Bundle the output files together
tar cf ${OUTPUT} ${ENCRYPTED_FILE} ${ENCRYPTED_KEY} ${RECIPIENT_METADATA} ${ENCRYPTED_SIGNATURE} ${SENDER_METADATA}

echo "Encryption Successful"

Decrypt

The final script is intended to decrypt bundles created by the above encryption script. It will:

  1. Extract the encrypted symmetric key
  2. Decrypt the symmetric key using the given secret key
  3. Extract the encrypted compressed file
  4. Use the decrypted symmetric key to decrypt the compressed file
  5. Decompress the file
  6. (optional) Extract and verify the signature using the supplied certificate

Run it like so:

$ ./decrypt.sh 
Usage: decrypt.sh <file> <recipient_private_key> [sender_certificate]

$ ./decrypt.sh test.txt.tar Alice.secret Bob.certificate 
Verified OK
Decryption Successful

In this case, the file was decrypted by Alice, and verified to be sent by Bob.

Here is the output:

Decryption Usage

The script:

(decrypt.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/bin/bash

# decrypt.sh
# Copyright (C) 2016 Joe Ruether jrruethe@gmail.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# Decrypts a file
# decrypt.sh <file> <recipient_private_key> [sender_certificate]
# 1) (Required) File to decrypt
# 2) (Required) Private key of the recipient
# 3) (Optional) Certificate of the sender

# Stop on any error
# set -e

# Declare an array of tasks to perform on exit
declare -a on_exit_items

# This function is run on exit
function on_exit()
{
    for i in "${on_exit_items[@]}"
    do
        eval $i
    done
}

# Add to the list of tasks to run on exit
function add_on_exit()
{
    local n=${#on_exit_items[*]}
    on_exit_items[$n]="$*"
    if [[ $n -eq 0 ]]; then
        trap on_exit EXIT
    fi
}

if [ "$#" -lt 2 ]; then
   echo "Usage: decrypt.sh <file> <recipient_private_key> [sender_certificate]"
   exit 1
fi

FILE=$1
RECIPIENT_PRIVATE_KEY=$2
SENDER_CERTIFICATE=$3

OUTPUT=${FILE//.tar/}
COMPRESSED_FILE=${OUTPUT}.bz2
ENCRYPTED_FILE=${COMPRESSED_FILE}.encrypted
SYMMETRIC_KEY=symmetric_key.bin
ENCRYPTED_KEY=${SYMMETRIC_KEY}.encrypted

SENDER_PUBLIC_KEY=${SENDER_CERTIFICATE//certificate/public}
SIGNATURE=${OUTPUT}.signature
ENCRYPTED_SIGNATURE=${SIGNATURE}.encrypted

# Unpack the encrypted key
tar xf ${FILE} ${ENCRYPTED_KEY} > /dev/null 2>&1
SUCCESS=$?
add_on_exit rm -f ${ENCRYPTED_KEY}

if [ ${SUCCESS} -ne 0 ]; then
   echo "Not a valid encrypted file"
   exit 1
fi

# Decrypt the symmetric key
openssl rsautl -decrypt -inkey ${RECIPIENT_PRIVATE_KEY} -in ${ENCRYPTED_KEY} -out ${SYMMETRIC_KEY} > /dev/null 2>&1
SUCCESS=$?

if [ ${SUCCESS} -ne 0 ]; then
   add_on_exit rm -f ${SYMMETRIC_KEY}
   echo "Unable to decrypt: Incorrect key"
   exit 1
else
   add_on_exit shred ${SYMMETRIC_KEY}
   add_on_exit rm -f ${SYMMETRIC_KEY}
fi

# Unpack the encrypted file
tar xf ${FILE} ${ENCRYPTED_FILE} > /dev/null 2>&1
SUCCESS=$?
add_on_exit rm -f ${ENCRYPTED_FILE}

if [ ${SUCCESS} -ne 0 ]; then
   echo "Not a valid encrypted file"
   exit 1
fi

# Decrypt the file
openssl enc -d -aes-256-cbc -in ${ENCRYPTED_FILE} -out ${COMPRESSED_FILE} -pass file:${SYMMETRIC_KEY}

# Decompress the file
bunzip2 -f ${COMPRESSED_FILE} > /dev/null 2>&1

# If the file is being verified
if [ ! -z ${SENDER_CERTIFICATE} ]; then

   # Unpack the signature
   tar xf ${FILE} ${ENCRYPTED_SIGNATURE} > /dev/null 2>&1
   SUCCESS=$?
   add_on_exit rm -f ${ENCRYPTED_SIGNATURE}

   if [ ${SUCCESS} -ne 0 ]; then
      echo "File is not signed"
   else
      # Get the public key
      add_on_exit rm -f ${SENDER_PUBLIC_KEY}
      openssl x509 -in ${SENDER_CERTIFICATE} -pubkey -noout > ${SENDER_PUBLIC_KEY}

      # Decrypt the signature
      openssl enc -d -aes-256-cbc -in ${ENCRYPTED_SIGNATURE} -out ${SIGNATURE} -pass file:${SYMMETRIC_KEY}
      add_on_exit shred ${SIGNATURE}
      add_on_exit rm -f ${SIGNATURE}

      # Verify the signature
      openssl dgst -sha256 -verify ${SENDER_PUBLIC_KEY} -signature ${SIGNATURE} ${OUTPUT}
      add_on_exit rm -f ${SIGNATURE}
   fi
fi

echo "Decryption Successful"

Cryptography, Scripts

Avatar

About The Author

Joe Ruether is the lead software engineer for the Multisensor Aircraft Tracking system at SRC Inc. He is an expert in C++ template metaprogramming and is interested in cryptography and open source software.


« Running Docker in Qubes

Comments