Skip to content

Instantly share code, notes, and snippets.

@pmontrasio
Last active December 23, 2021 08:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pmontrasio/189b17fed34a5674a464 to your computer and use it in GitHub Desktop.
Save pmontrasio/189b17fed34a5674a464 to your computer and use it in GitHub Desktop.
Compress a file, encrypt it, upload it to S3. Download, decrypt, uncompress.
#!/bin/env/ruby
# gem "aws-sdk", "~> 2"
require "rubygems"
require "zlib"
require "rubygems/package"
require "securerandom"
gem "aws-sdk"
require "aws-sdk"
# The uploaded tar.gz at key in bucket contains
# * the compressed and encrypted file, with a random symmetric key
# * the random symmetric key, encrypted with an asymmetric private key
# * the initialization vector of the symmetric cipher, encrypted with an asymmetric private key
# The tar.gz is not encrypted, only the files inside it are.
# It's compressed because the tar format introduces some metadata that can be cut down a bit with compression.
#
# Generate the asymmetric keys with
#
# $ irb
# require "openssl"
# key = OpenSSL::PKey::RSA.new 4096
# open 'private_key.pem', 'w' do |io| io.write key.to_pem end
# open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end
# exit
#
# Run the demo with
#
# $ export PUBLIC_KEY=public_key.pem
# $ export PRIVATE_KEY=private_key.pem
# $ export BUCKET_NAME=your-amazon-bucket
# $ gem install "aws-sdk" -v '~> 2'
# $ ruby ./safe_s3.rb
#
# The demo code is at the end of this file.
class SafeS3
def initialize
bucket_name = ENV["BUCKET_NAME"]
raise Exception.new("Add the AWS bucket name. Export its name in BUCKET_NAME.") unless bucket_name
public_key_file = ENV["PUBLIC_KEY"]
raise Exception.new("Add a public key to encrypt data. Export its file name in PUBLIC_KEY.") unless public_key_file
private_key_file = ENV["PRIVATE_KEY"]
raise Exception.new("Add a private key to decrypt data. Export its file name in PRIVATE_KEY.") unless private_key_file
public_key_pem = File.read(public_key_file)
@public_key = OpenSSL::PKey::RSA.new public_key_pem
private_key_pem = File.read(private_key_file)
@private_key = OpenSSL::PKey::RSA.new private_key_pem
@cipher = "aes-256-cbc"
s3 = Aws::S3::Client.new
resource = Aws::S3::Resource.new(client: s3)
@bucket = resource.bucket(bucket_name)
end
def upload(text)
text_tar_gz = encrypt(text)
uuid = SecureRandom.uuid
checksum = Digest::SHA256.hexdigest(text_tar_gz)
key = "#{uuid}-#{checksum}"
s3_obj = @bucket.object(key)
s3_obj.put body: text_tar_gz
key
end
def download(key)
s3_object = @bucket.object(key)
decrypt(s3_object.get.body.read)
end
def delete(key)
@bucket.delete_objects delete: { objects: [{ key: key }] }
end
private
def encrypt(text)
text_gz = Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION)
cipher = OpenSSL::Cipher.new(@cipher)
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
encrypted_text = cipher.update(text_gz)
encrypted_text << cipher.final
tar_file = StringIO.new("")
Gem::Package::TarWriter.new(tar_file) do |tar|
tar.add_file("encrypted", 0400) { |t| t.write(encrypted_text) }
tar.add_file("key", 0400) { |t| t.write(@public_key.public_encrypt(key)) }
tar.add_file("iv", 0400) { |t| t.write(@public_key.public_encrypt(iv)) }
end
tar_file.rewind
tar_gz = StringIO.new
gz = Zlib::GzipWriter.new(tar_gz, Zlib::BEST_COMPRESSION)
gz.write(tar_file.string)
gz.close
tar_gz.string
end
def decrypt(tar_gz)
tgz = StringIO.new(tar_gz)
gz = Zlib::GzipReader.new(tgz)
tar_file = StringIO.new(gz.read)
gz.close
encrypted_file = nil
key = nil
iv = nil
Gem::Package::TarReader.new(tar_file) do |tar|
tar.each do |file|
case file.full_name
when "encrypted"
encrypted_file = file.read
when "key"
key = @private_key.private_decrypt(file.read)
when "iv"
iv = @private_key.private_decrypt(file.read)
else
raise Exception.new("The archive contains some extra file. Corrupted or altered?")
end
end
end
cipher = OpenSSL::Cipher.new(@cipher)
cipher.decrypt
cipher.key = key
cipher.iv = iv
file_gz = cipher.update(encrypted_file)
file_gz << cipher.final
Zlib::Inflate.inflate(file_gz)
end
end
s3 = SafeS3.new
key = s3.upload("content")
p key
p s3.download(key)
s3.delete(key)
s3 = SafeS3.new
key = s3.upload(File.read("safe_s3.rb"))
p key
p s3.download(key)
s3.delete(key)
@jaswinder97
Copy link

does this compress a big object into a smaller size with zipping? for example, if the object size is 600K, then how much compression it does?

@pmontrasio
Copy link
Author

It's the usual gzip compression. About 10 times smaller for text, maybe nothing for movies or MP3 because they are already compressed and there is little redundancy left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment