Last active
December 23, 2021 08:37
-
-
Save pmontrasio/189b17fed34a5674a464 to your computer and use it in GitHub Desktop.
Compress a file, encrypt it, upload it to S3. Download, decrypt, uncompress.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.