REBOL
Docs Blog Get-it

How to Encrypt Strings and Files

Revised: 12-Mar-2024
Original: 4-May-2001

Contents

How to Run the Examples
Encrypting a String
Decrypting a String
Creating a Common Crypt Function
Creating a Good Encryption Key
Password Based Keys
Computer Generated Keys
Encrypting a File
Decrypting a File
Adding Compression
Building an Encryption Utility
Encrypted Email
Decrypting Email
Encryption Strength
Encryption Methods
Encryption Padding

How to Run the Examples

To run any of the examples that are shown below, open a text editor and create a REBOL header line such as:

REBOL [Title: "Example"]

Then simply cut the example text and paste it after this line. Save the text file and give it a name, such as example.r. Then run the file just as you would run any REBOL file.

Always keep an unencrypted copy of everything that you encrypt. Otherwise, if you forget the encryption key, or lose the key, or if your encrypted data become damaged, you may lose all or part of your data.

Detailed information about using encryption is provided in the REBOL Encryption Document.

Encrypting a String

View/Pro has a variety of built-in encryption functions. The examples below use symmetric key encryption. This type of encryption is commonly used to encrypt files where the same key is used for both encryption and decryption.

Here is a simple example that encrypts a string. To specify the encryption, you need to open a port with a port object specification. The specification looks like:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: #{1122334455667788}
    padding: true
]

The port is opened and returned similar to any other file or network port. The scheme specifies that you want an encryption port. The direction word tells the port whether you are encrypting or decrypting. The key is a binary number that is your secret key for encryption. This same key is required to decrypt your data. (More about this below.) The padding field specifies that you want REBOL to properly maintain the length of your data.

To encrypt a string you use the same series functions that you use on file or network ports. The example below encrypts the string.

insert port "This is a string"
update port
data: copy port

First you insert the string into the port. Next, you signal that you want the port to update itself, processing the data that you provided. Finally, you copy the encrypted data out of the port.

probe data

Notice that the data is binary at this point.

Don't forget to close the port when you are finished:

close port

Decrypting a String

To decrypt the string that you encoded above, all you need to change is the direction of the port. You use the same key and padding. The example below is identical to that above, but specifies DECRYPT for the direction:

port: open [
    scheme: 'crypt
    direction: 'decrypt
    key: #{1122334455667788}
    padding: true
]

The data from the above example is used as input to this decryption port:

insert port data
update port
result: copy port
close port

The result of the operation is a binary value. To return it to a string, you must convert it:

str: to-string result
probe string

Creating a Common Crypt Function

Notice that the encryption and decryption code above is identical, with the exception of the encryption direction. Because of this, it is helpful to define a function that performs most of the work.

The code below defines a function that is used in all the examples that follow:

crypt: func [
    "Encrypts or decrypts data and returns the result."
    data [any-string!] "Data to encrypt or decrypt"
    akey [binary!] "The encryption key"
    /decrypt "Decrypt the data"
    /binary "Produce binary decryption result."
    /local port
][
    port: open [
        scheme: 'crypt
        direction: pick [encrypt decrypt] not decrypt
        key: akey
        padding: true
    ]
    insert port data
    update port
    data: copy port
    close port
    if all [decrypt not binary] [data: to-string data]
    data
]

A full function header is used so you can get help on the function later:

help crypt

The previous examples now become:

data: crypt "This is a string" #{1122334455667788}

probe crypt/decrypt data #{1122334455667788}

This makes encryption and decryption within your code a lot easier.

Creating a Good Encryption Key

The strength of encryption really comes down to the size and quality of your encryption key. Just like passwords, if a key is short and obvious, it gives someone a greater chance at cracking it.

There are two ways to create a key. You can specify your own key as a string or binary value, or you can have a program generate a key.

Password Based Keys

The examples above used a binary key that was specified within the script:

key: #{1122334455667788}

By default, the strength of the encryption is determined by the length of the key. The key above is 8 bytes, which is 64 bits. So, you can use a much longer binary string if you need greater strength. This key is 128 bits:

key: #{112233445566778899AABBCCDDEEFF00}

A key can also be a string:

key: "There ain't no such thing as a free lunch."

The above keys can be used, however they are far from optimal because they are too simple in their structure. The best way to create a key is to pass the string through an "encoder" that produces a higher quality key.

Here is an example of an easy way to encode a key:

key: checksum/secure "This is my pass string."

Using CHECKSUM with the /SECURE refinement takes that input string and produces a cryptographically secure binary string. This result makes a much better encryption key.

Note that the string returned from CHECKSUM is 160 bits. You can reduce its size with COPY/PART. The example below trims the key to 64 bits (8 bytes):

key64: copy/part key 8

Here is a handy function that requests the a key phrase from the user, encodes it, and returns it as a result.

request-key: has [pass] [
    pass: request-text/title "Enter a pass-phrase:"
    if pass [checksum/secure pass]
]

To try it out, do this:

probe request-key

The function returns NONE if the user cancels the request.

Computer Generated Keys

Another way to create a key is to have a program generate it. This can be done in a variety of ways, but here is a simple and dependable way that creates a quality key:

key: checksum/secure form now/precise

The NOW/PRECISE function returns the date, a detailed time, and the timezone. The FORM converts it to a string which is then encoded with CHECKSUM/SECURE.

When a key is generated by a program, it needs to be stored somewhere, such as on a floppy disk. Store it away from the encrypted data. If you save the key to your disk or send it in an email, then you might as well not encrypt your data.

Encrypting a File

The functions created above can be used to encrypt a file. Here is an example that takes a file called secrets.r and writes a file called secrets.bin.

file: %secrets.r
key: request-key
data: crypt read/binary file key
write/binary %secrets.bin data

Notice that the resulting file is a binary file. If you forget to use WRITE/BINARY you will damage your encrypted data, and it will not be possible to decrypt it later. Remember that.

Decrypting a File

The file encrypted above can be decrypted with this example:

file: %secrets.bin
key: request-key
data: crypt/decrypt/binary read/binary file key
write/binary %secrets2.r data

The secrets2.r file is identical to the original file that was encrypted.

Adding Compression

The encrypted file produced above is encoded and cannot be used unless it is decrypted. Because of this, it is often useful to compress the data before it is encrypted. Compressing the data speeds up encryption and decryption processes.

crypt: func [
    "Encrypts or decrypts with compression. Returns result."
    data [any-string!] "Data to encrypt or decrypt"
    akey [binary!] "The encryption key"
    /decrypt "Decrypt the data"
    /binary "Produce binary decryption result."
    /local port
][
    port: open [
        scheme: 'crypt
        direction: pick [encrypt decrypt] not decrypt
        key: akey
        padding: true
    ]
    if not decrypt [data: compress data]
    insert port data
    update port
    data: copy port
    close port
    if decrypt [
        data: decompress data
        if not binary [data: to-string data]
    ]
    data
]

When you encrypt a file, it is compressed:

file: %secrets.r
key: request-key
data: crypt read/binary file key
write/binary %secrets.bin data

print [size? %secrets.r  size? %secrets.bin]

When you decrypt the file, it is decompressed:

file: %secrets.bin
key: request-key
data: crypt/decrypt/binary read/binary file key
write/binary %secrets2.r data

Note that if your data becomes corrupted, the decompression will fail with an error message.

Building an Encryption Utility

Here is a simple, useful encryption utility that uses the functions above. It asks whether you want encryption or decryption, then lets you select one or more files to be processed.

op: request ["Select action:" "Encrypt" "Decrypt" "Cancel"]
if none? op [quit]

action: pick ["Encrypt" "Decrypt"] op

files: request-file/title join "Select Files to" action action
if none? files [quit]

key: request-key
if none? key [quit]

foreach file files [
    data: read/binary file key
    data: either op [crypt data key][crypt/decrypt/binary data key] 
    write/binary file data
]

This utility is posted in the REBOL [?script library as crypt.r?].

Encrypted Email

As easily as you encrypt a file, you can also encrypt an email. Here is a panel that lets you send an encrypted file to someone via email. It is a simple example that embeds the encrypted data into the email message body. It does not use the standard email file attachment format.

view layout [
    style lab label right 80x24
    across space 0x4
    vh2 "Send Encrypted Email File:" return
    lab "To:"      f-to: field return
    lab "Subject:" f-sub: field return
    lab "Key:"     f-key: field hide return
    lab "File:"    f-file: text 200x24 white black middle
        "click to pick" [f-file/text: request-file/keep] return
    lab
    button "Send" [unview]
    button "Close" [quit]
]

The panel looks like:


It is followed with the code:

dest: to-email f-to/data
file: to-file f-file/data
key: checksum/secure f-key/data
subject: f-sub/data

msg: enbase/base crypt read/binary file 64
send/subject email msg subject

The email is sent with the given subject line, and the body of the email contains the base-64 encrypted data.

Decrypting Email

When the above email is received, it needs to be decrypted. Because email is normally received by your email client program, the simplest way to decrypt the email message is to cut and paste the body of the email into the data window in the program below:

view layout [
    style lab label right 80x24
    across space 0x4
    vh2 "Decrypt Email File:" return
    lab "Key:"     f-key: field hide return
    lab "File:"    f-file: text 200x24 white black middle
        "click to pick" [f-file/text: request-file/keep] return
    lab "Data:"    f-data: area return
    lab
    button "Decrypt" [unview]
    button "Close" [quit]
]

The panel looks like:


It is followed with the code:

file: to-file f-file/data
key: checksum/secure f-key/data
data: f-data/data

data: debase/base data 64
write/binary file crypt/decrypt/binary data key

The decrypted data is written to the file name that you specify.

Encryption Strength

In the examples above the length of your key determines the strength of the encryption that is used. In the above examples CHECKSUM/SECURE is used, and the length of the key is 160 bits.

You can control the strength of the encryption by specifying it explicitly in the port object. For instance, to reduce the strength to 64 bits, you can write:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: akey
    strength: 64
    padding: true
]

The maximum strength allowed for encryption is regulated by US export laws. If you are using an international version of REBOL, your encryption strength may or may not be restricted, depending on your country.

The function below indicates whether you have full strength, export strength, or no strength.

print crypt-strength?

If you are using an export version of REBOL, then the strength of your encryption is decreased by reducing the number of bits in the key. To see how many bits were used, you can check the port strength after the port has been opened:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: akey
    strength: 512
    padding: true
]

print port/strength

Encryption Methods

The examples above use the default encryption method for symmetric key encryption. The algorithm is called Blowfish and it is well established.

The new US Advanced Encryption Standard is also available. The algorithm is called Rijndael. It can be set within the port specification with:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: akey
    strength: 512
    algorithm: 'rijndael
    padding: true
]

print port/strength

Other types of encryption are also available. REBOL supports RSA public key encryption, DSA digital signature algorithm, and DH (Diffie Hellman) key exchange. These are covered in the REBOL Encryption Document.

Encryption Padding

The examples above use padding to allow the results of decryption to be the same length as the data that was encrypted. However, if you require compatibility with data that was encrypted outside of REBOL, or if your encrypted data is decrypted outside of REBOL, then you need to disable padding for compatibility.

The example below disables padding:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: akey
    padding: false
]

By default, padding is disabled, so you can simply write:

port: open [
    scheme: 'crypt
    direction: 'encrypt
    key: akey
]

See the REBOL Encryption Document for a complete discussion about padding.

About | Contact | PrivacyREBOL Technologies 2024