My current contract involves setting up a serverless processing pipeline in AWS, storing all the data in S3. During development of the system, we have been working with a bucket that does not have versioning or encryption enabled, but we have reached a point where I have asked if the bucket ought to be encrypted.
We are dealing with electrical meter data (electricity consumption), so it is not PII but it is still confidential. Server-side encryption (SSE) on S3 only really protects against an attack vector involving access to Amazon’s physical storage, which is vastly more challenging than this data would be worth; however, enabling SSE on a bucket is trivial, so the configuration cost is essentially zero. With this in mind, encrypting the bucket seems like a reasonable choice to make.
The system architect agreed with this assessment, with the caveat that he would like to be sure that having encryption enabled doesn’t degrade performance too far. I had a brief search of the web to find any other evaluations of performance impacts, and found
Since these didn’t supply any numbers, I figured: why not do some basic measurements myself?
All our data access is from within AWS (Lambda and Glue), so I needed to perform the measurements from there. With that in mind, I created this Lambda code:
import io
import time
import uuid
import boto3
MB = 2**20
BASE_MEMORY_REQUIRED = 80 # MB
def create_file(size_mb: int) -> io.BytesIO:
"""
Create and return a file-like buffer of 'size' random megabytes.
"""
buffer = io.BytesIO()
with open('/dev/urandom', 'rb') as random_source:
for _ in range(size_mb):
buffer.write(random_source.read(1 * MB))
buffer.seek(0)
return buffer
def lambda_handler(event: dict, context):
file_size_mb = event['file_size_mb']
unencrypted_bucket = event['unencrypted_bucket']
encrypted_bucket = event['encrypted_bucket']
iterations = event['iterations']
# Check that the lambda has been set up properly
if int(context.memory_limit_in_mb) <= file_size_mb + BASE_MEMORY_REQUIRED:
raise ValueError("Insufficient memory allocated")
def upload(bucket):
big_file.seek(0)
s3.put_object(
Bucket=bucket,
Key=file_name,
Body=big_file,
)
def download(bucket):
obj = s3.get_object(
Bucket=bucket,
Key=file_name,
)
obj['Body'].read()
s3 = boto3.client('s3')
unencrypted_upload_times = []
encrypted_upload_times = []
unencrypted_download_times = []
encrypted_download_times = []
print(f"Starting {iterations} uploads and downloads")
for i in range(iterations):
big_file = create_file(file_size_mb)
file_name = uuid.uuid4().hex
# Upload the data and time the operation (twice),
# then download the data and time it (twice)
t0 = time.time()
upload(encrypted_bucket)
encrypted_upload_times.append(time.time() - t0)
print(f"{i}: uploaded to encrypted bucket")
t0 = time.time()
upload(unencrypted_bucket)
unencrypted_upload_times.append(time.time() - t0)
print(f"{i}: uploaded to unencrypted bucket")
# Try to avoid doubling up on in-memory storage of the large files
big_file.truncate(0)
t0 = time.time()
download(encrypted_bucket)
encrypted_download_times.append(time.time() - t0)
print(f"{i}: downloaded from encrypted bucket")
t0 = time.time()
download(unencrypted_bucket)
unencrypted_download_times.append(time.time() - t0)
print(f"{i}: downloaded from unencrypted bucket")
result = {
"iterations": iterations,
"size": file_size_mb,
"encrypted_upload_times": encrypted_upload_times,
"unencrypted_upload_times": unencrypted_upload_times,
"encrypted_download_times": encrypted_download_times,
"unencrypted_download_times": unencrypted_download_times,
}
print(result)
return {
'statusCode': 200,
'body': result,
'headers': {'Content-Type': 'application/json'}
}
and triggered it directly from the Lambda console “test” utility with the payload
{
"file_size_mb": 800,
"unencrypted_bucket": "alex-test-unencrypted",
"encrypted_bucket": "alex-test-encrypted",
"iterations": 10
}
I only requested 10 iterations because even that took nearly ten minutes to run with the 800MB file size that I specified. So I called it 5 times to obtain 50 measurements. The results looked like this:
Upload (s) | Download (s) | ||
---|---|---|---|
median: | 11.7 | 11.6 | |
Unencrypted: | mean: | 12.5 | 11.6 |
std. dev.: | 2.9 | 0.4 | |
median: | 11.7 | 10.9 | |
Encrypted: | mean: | 11.9 | 10.7 |
std. dev.: | 0.8 | 0.5 |
Which suggests that upload times are largely unaffected by the encryption setting, and that download times are interestingly faster for encrypted files (at least for these files).
In conclusion, enabling encryption on the S3 bucket has no cost (direct financial or performance) so there is no reason not to do so. Note, however, that this measurement only used SSE-S3, not SSE-KMS. From some brief searching of the web, SSE-KMS can run into performance problems in terms of rate of files uploaded/downloaded (as opposed to rate of bytes uploaded/downloaded) because it requires S3 to make API calls to KMS to obtain the key for each operation; this can be largely mitigated by using a Bucket Key, but that’s well beyond the scope of this brief investigation.
Appendix - the raw time measurements (in seconds):
Encrypted upload | Unencrypted upload | Encrypted download | Unencrypted download |
---|---|---|---|
11.6940746307373 | 11.6849784851074 | 10.9234869480133 | 11.7144949436188 |
11.6625287532806 | 11.6226992607117 | 11.4408304691315 | 11.6158993244171 |
11.7032141685486 | 11.7632384300232 | 10.8988852500916 | 11.7233171463013 |
12.1810274124146 | 11.6629092693329 | 10.9905335903168 | 12.8688085079193 |
11.6816043853760 | 11.6620612144470 | 10.8789787292480 | 11.6937589645386 |
11.7196235656738 | 11.6734302043915 | 10.9571630954742 | 11.6186110973358 |
11.6261374950409 | 11.6171290874481 | 9.2294242382050 | 11.2199978828430 |
12.1792688369751 | 11.6139698028564 | 10.9129040241241 | 11.6814973354340 |
11.6769218444824 | 11.6034467220306 | 10.8799097537994 | 11.7172236442566 |
11.6727154254913 | 11.7504541873932 | 10.8840353488922 | 11.6745781898499 |
11.6735568046570 | 11.6607069969177 | 10.9813044071198 | 11.6213848590851 |
11.6849887371063 | 11.6179919242859 | 10.8705642223358 | 11.6690998077393 |
12.0919978618622 | 11.6796560287476 | 9.0999648571014 | 11.3372871875763 |
11.6507017612457 | 11.6462154388428 | 10.9386730194092 | 11.7185146808624 |
11.5898842811584 | 11.6660833358765 | 10.9372851848602 | 11.6424448490143 |
11.6603825092316 | 11.6032352447510 | 9.1814439296722 | 11.1970849037170 |
11.6504487991333 | 20.8611717224121 | 10.9414134025574 | 11.5785925388336 |
11.5966084003448 | 11.6695580482483 | 11.0593311786652 | 11.7907378673553 |
11.6372516155243 | 11.6758489608765 | 11.0018763542175 | 11.7571446895599 |
11.8446044921875 | 11.6281900405884 | 10.9578194618225 | 9.5198469161987 |
11.7846033573151 | 11.8456094264984 | 9.2133951187133 | 10.9204108715057 |
11.6085646152496 | 11.6443903446198 | 10.7986392974854 | 11.5597372055054 |
11.7643485069275 | 11.6076791286469 | 10.8625876903534 | 11.5612387657166 |
11.7053418159485 | 11.6414282321930 | 10.8453776836395 | 11.6012265682220 |
11.6800470352173 | 11.6984360218048 | 10.8957929611206 | 11.5266029834747 |
11.6613471508026 | 11.6728124618530 | 10.8214366436005 | 11.6234931945801 |
11.6896800994873 | 22.1839056015015 | 10.8185594081879 | 11.6012597084045 |
11.7522304058075 | 11.6759369373322 | 10.9014334678650 | 11.5810294151306 |
11.6505093574524 | 11.6274704933167 | 10.8986401557922 | 11.5984861850739 |
11.6225833892822 | 11.6417267322540 | 10.8359308242798 | 11.5892403125763 |
11.7100932598114 | 11.5911505222321 | 10.8719291687012 | 11.5881307125092 |
11.6352610588074 | 11.6236982345581 | 10.8744866847992 | 11.5054509639740 |
12.3622174263000 | 11.5923130512238 | 10.8601109981537 | 11.4798181056976 |
11.6519224643707 | 11.6992461681366 | 10.8413724899292 | 11.4571144580841 |
11.9042441844940 | 11.7184102535248 | 11.0654361248016 | 11.7385985851288 |
11.8565964698792 | 11.6578516960144 | 10.9584598541260 | 11.5479769706726 |
12.9413495063782 | 11.6869413852692 | 10.8612637519836 | 11.6412565708160 |
11.6435585021973 | 11.7229723930359 | 10.8753590583801 | 11.5985469818115 |
11.6253650188446 | 11.6678795814514 | 10.8005053997040 | 11.5813326835632 |
17.4464807510376 | 11.6508176326752 | 10.8987245559692 | 11.5757186412811 |
11.6235389709473 | 11.6626203060150 | 10.7445497512817 | 11.5973939895630 |
11.7414813041687 | 11.6903252601624 | 9.2054750919342 | 11.2935853004456 |
11.7315373420715 | 25.7158513069153 | 11.2086730003357 | 11.6013922691345 |
11.6676566600800 | 11.6517312526703 | 10.7980945110321 | 11.5454034805298 |
11.7108561992645 | 19.0991778373718 | 10.8938765525818 | 11.5185081958771 |
11.7484521865845 | 11.6650342941284 | 10.9214606285095 | 11.5405542850494 |
12.2797617912293 | 11.6601886749268 | 10.8545019626617 | 11.5244343280792 |
11.6670072078705 | 11.5868990421295 | 10.8798396587372 | 11.5391335487366 |
11.7142891883850 | 11.6777644157410 | 10.8001849651337 | 11.5678985118866 |
11.6488909721375 | 11.6711585521698 | 10.8583946228027 | 11.5788042545319 |