-
Notifications
You must be signed in to change notification settings - Fork 58
Add AES encrypt/decrypt example #492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Firstyear
wants to merge
4
commits into
parallaxsecond:main
Choose a base branch
from
Firstyear:20240203-aes-encrypt-decrypt-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// Copyright 2024 Contributors to the Parsec project. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/* | ||
* This example demonstrates how to use AES for symmetric encryption and decryption | ||
*/ | ||
|
||
use tss_esapi::{ | ||
attributes::ObjectAttributesBuilder, | ||
interface_types::{ | ||
algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode}, | ||
key_bits::AesKeyBits, | ||
reserved_handles::Hierarchy, | ||
}, | ||
structures::{ | ||
CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder, | ||
SymmetricCipherParameters, SymmetricDefinitionObject, | ||
}, | ||
Context, TctiNameConf, | ||
}; | ||
|
||
use std::convert::TryFrom; | ||
|
||
fn main() { | ||
// Create a new TPM context. This reads from the environment variable `TPM2TOOLS_TCTI` or `TCTI` | ||
// | ||
// It's recommended you use `TCTI=device:/dev/tpmrm0` for the linux kernel | ||
// tpm resource manager. | ||
let mut context = Context::new( | ||
TctiNameConf::from_environment_variable() | ||
.expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"), | ||
) | ||
.expect("Failed to create Context"); | ||
|
||
// This example won't go over the process to create a new parent. For more detail see `examples/hmac.rs`. | ||
|
||
let primary = create_primary(&mut context); | ||
|
||
// Begin to create our new AES symmetric key | ||
|
||
let object_attributes = ObjectAttributesBuilder::new() | ||
.with_fixed_tpm(true) | ||
.with_fixed_parent(true) | ||
.with_st_clear(false) | ||
.with_sensitive_data_origin(true) | ||
.with_user_with_auth(true) | ||
.with_sign_encrypt(true) | ||
.with_decrypt(true) | ||
// Note that we don't set the key as restricted. | ||
.build() | ||
.expect("Failed to build object attributes"); | ||
|
||
let aes_params = SymmetricCipherParameters::new(SymmetricDefinitionObject::Aes { | ||
key_bits: AesKeyBits::Aes128, | ||
mode: SymmetricMode::Cbc, | ||
}); | ||
|
||
let key_pub = PublicBuilder::new() | ||
.with_public_algorithm(PublicAlgorithm::SymCipher) | ||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256) | ||
.with_object_attributes(object_attributes) | ||
.with_symmetric_cipher_parameters(aes_params) | ||
.with_symmetric_cipher_unique_identifier(Digest::default()) | ||
.build() | ||
.unwrap(); | ||
|
||
let (enc_private, public) = context | ||
.execute_with_nullauth_session(|ctx| { | ||
ctx.create(primary.key_handle, key_pub, None, None, None, None) | ||
.map(|key| (key.out_private, key.out_public)) | ||
}) | ||
.unwrap(); | ||
|
||
// The data we wish to encrypt. Be aware that there is a limit to the size of this data | ||
// that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a | ||
// content encryption key (CEK), which can be decrypted and released and then used to decrypt | ||
// the actual data in question outside of the TPM. | ||
// | ||
// TPMs also tend to be "slower" for encryption/decryption, so you should consider the | ||
// CEK pattern for performance reasons. | ||
let data_to_encrypt = "TPMs are super cool, you should use them!" | ||
.as_bytes() | ||
.to_vec(); | ||
|
||
eprintln!("{:?}", data_to_encrypt.len()); | ||
|
||
// Input data needs to always be a multiple of the AES block size, in this case which is 16 | ||
// bytes for AES-128-CBC. Normally you *MUST* implement a secure padding scheme such as pkcs7 | ||
// but in this example we will *manually* pad the data. | ||
|
||
// WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this | ||
// in production. | ||
const AES_BLOCK_SIZE: usize = 16; | ||
|
||
let need_k_bytes = AES_BLOCK_SIZE - (data_to_encrypt.len() % AES_BLOCK_SIZE); | ||
// PKCS7 always pads to remove ambiguous situations. | ||
let need_k_bytes = if need_k_bytes == 0 { | ||
AES_BLOCK_SIZE | ||
} else { | ||
need_k_bytes | ||
}; | ||
|
||
let new_len = data_to_encrypt.len() + need_k_bytes; | ||
|
||
let mut padded_data_to_encrypt = data_to_encrypt.to_vec(); | ||
padded_data_to_encrypt.resize(new_len, need_k_bytes as u8); | ||
|
||
let padded_data_to_encrypt = MaxBuffer::try_from(padded_data_to_encrypt).unwrap(); | ||
|
||
// Padding always has to be added in pkcs7 to make it unambiguous. | ||
assert_ne!( | ||
data_to_encrypt.as_slice(), | ||
padded_data_to_encrypt.as_slice() | ||
); | ||
// END WARNING | ||
|
||
// AES requires a random initial_value before any encryption or decryption. This must | ||
// be persisted with the encrypted data, else decryption can not be performed. | ||
// This value MUST be random, and should never be reused between different encryption | ||
// operations. | ||
let initial_value = context | ||
.execute_with_nullauth_session(|ctx| { | ||
ctx.get_random(InitialValue::MAX_SIZE) | ||
.and_then(|random| InitialValue::try_from(random.to_vec())) | ||
}) | ||
.unwrap(); | ||
|
||
// Since AES is symmetric, we need the private component of the key to encrypt or decrypt | ||
// any values. | ||
let (encrypted_data, _initial_value) = context | ||
.execute_with_nullauth_session(|ctx| { | ||
let aes_key = ctx | ||
.load(primary.key_handle, enc_private.clone(), public.clone()) | ||
.unwrap(); | ||
|
||
let decrypt = false; | ||
|
||
ctx.encrypt_decrypt_2( | ||
aes_key, | ||
decrypt, | ||
SymmetricMode::Cbc, | ||
padded_data_to_encrypt.clone(), | ||
initial_value.clone(), | ||
) | ||
}) | ||
.unwrap(); | ||
|
||
// The data is now encrypted. | ||
println!("encrypted_data = {:?}", encrypted_data); | ||
assert_ne!(encrypted_data.as_slice(), padded_data_to_encrypt.as_slice()); | ||
|
||
// Decryption is the identical process with the "decrypt" flag set to true. | ||
let (decrypted_data, _initial_value) = context | ||
.execute_with_nullauth_session(|ctx| { | ||
let aes_key = ctx | ||
.load(primary.key_handle, enc_private.clone(), public.clone()) | ||
.unwrap(); | ||
|
||
let decrypt = true; | ||
|
||
ctx.encrypt_decrypt_2( | ||
aes_key, | ||
decrypt, | ||
SymmetricMode::Cbc, | ||
encrypted_data.clone(), | ||
initial_value, | ||
) | ||
}) | ||
.unwrap(); | ||
|
||
// Now we have to un-pad the output. | ||
if decrypted_data.is_empty() { | ||
panic!("Should not be empty"); | ||
} | ||
|
||
// WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this | ||
// in production. | ||
|
||
let last_byte = decrypted_data.len() - 1; | ||
let k_byte = decrypted_data[last_byte]; | ||
// Since pkcs7 padding repeats this byte k times, we check that this byte | ||
// is repeated as many times as expected. In theory we don't need this check | ||
// but it's better to be defensive. | ||
|
||
if k_byte as usize > AES_BLOCK_SIZE { | ||
panic!("Invalid pad byte, exceeds AES_BLOCK_SIZE"); | ||
} | ||
|
||
for i in 0..k_byte { | ||
if decrypted_data[last_byte - i as usize] != k_byte { | ||
panic!("Invalid pad byte, is not equal to k_byte"); | ||
} | ||
} | ||
|
||
let truncate_to = decrypted_data.len().checked_sub(k_byte as usize).unwrap(); | ||
let mut decrypted_data = decrypted_data.to_vec(); | ||
decrypted_data.truncate(truncate_to); | ||
|
||
// END WARNING | ||
|
||
println!("data_to_encrypt = {:?}", data_to_encrypt); | ||
println!("decrypted_data = {:?}", decrypted_data); | ||
// They are the same! | ||
assert_eq!(data_to_encrypt, decrypted_data); | ||
} | ||
|
||
fn create_primary(context: &mut Context) -> CreatePrimaryKeyResult { | ||
let object_attributes = ObjectAttributesBuilder::new() | ||
.with_fixed_tpm(true) | ||
.with_fixed_parent(true) | ||
.with_st_clear(false) | ||
.with_sensitive_data_origin(true) | ||
.with_user_with_auth(true) | ||
.with_decrypt(true) | ||
.with_restricted(true) | ||
.build() | ||
.expect("Failed to build object attributes"); | ||
|
||
let primary_pub = PublicBuilder::new() | ||
.with_public_algorithm(PublicAlgorithm::SymCipher) | ||
.with_name_hashing_algorithm(HashingAlgorithm::Sha256) | ||
.with_object_attributes(object_attributes) | ||
.with_symmetric_cipher_parameters(SymmetricCipherParameters::new( | ||
SymmetricDefinitionObject::AES_128_CFB, | ||
)) | ||
.with_symmetric_cipher_unique_identifier(Digest::default()) | ||
.build() | ||
.unwrap(); | ||
|
||
context | ||
.execute_with_nullauth_session(|ctx| { | ||
ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None) | ||
}) | ||
.unwrap() | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.