diff --git a/aws-lc-rs/src/aead.rs b/aws-lc-rs/src/aead.rs
index 1667b5d4bc2..1c0e743bab0 100644
--- a/aws-lc-rs/src/aead.rs
+++ b/aws-lc-rs/src/aead.rs
@@ -55,7 +55,7 @@
 //! # Nonce Sequence APIs
 //!
 //! The [`UnboundKey`], [`OpeningKey`], [`SealingKey`], and [`LessSafeKey`] types are the
-//! AEAD API's provided for compatability with the original *ring* API.
+//! AEAD API's provided for compatibility with the original *ring* API.
 //!
 //! Users should prefer [`RandomizedNonceKey`] which provides a simplified experience around
 //! Nonce construction.
@@ -113,10 +113,77 @@
 //!
 //! assert_eq!(plaintext, decrypted_plaintext);
 //! ```
+//!
+//! ## Prepared Nonce API's with Nonce Sequence
+//!
+//! If you prefer to use the [NonceSequence] based API's, and need to know the [Nonce] explicit nonce used for a
+//! cryptographic key operation operation, then [SealingKeyPreparedNonce] and
+//! [OpeningKeyPreparedNonce] are available to you.
+//!
+//! ```rust
+//! # use std::error::Error;
+//! #
+//! # fn main() -> Result<(), Box<dyn Error>> {
+//! use aws_lc_rs::aead::{
+//!     nonce_sequence::Counter32Builder, Aad, BoundKey, OpeningKey, SealingKey, UnboundKey,
+//!     AES_128_GCM,
+//! };
+//! use std::vec::Vec;
+//!
+//! let key_bytes = &[
+//!     0xa5, 0xf3, 0x8d, 0x0d, 0x2d, 0x7c, 0x48, 0x56, 0xe7, 0xf3, 0xc3, 0x63, 0x0d, 0x40, 0x5b,
+//!     0x9e,
+//! ];
+//!
+//! // Create AES-128-GCM SealingKey
+//! let mut sealing_key = SealingKey::new(
+//!     UnboundKey::new(&AES_128_GCM, key_bytes)?,
+//!     Counter32Builder::new()
+//!         .identifier([0, 1, 2, 3, 4, 5, 6, 7])
+//!         .build(),
+//! );
+//!
+//! // Create AES-128-GCM OpeningKey
+//! let mut opening_key = OpeningKey::new(
+//!     UnboundKey::new(&AES_128_GCM, key_bytes)?,
+//!     Counter32Builder::new()
+//!         .identifier([0, 1, 2, 3, 4, 5, 6, 7])
+//!         .build(),
+//! );
+//!
+//! let message = "test message";
+//! let mut in_out = Vec::from(message);
+//!
+//! // Create a SealingKeyPreparedNonce which consumes a nonce from the underlying sequence
+//! let seal_prepared_nonce = sealing_key.prepare_nonce()?;
+//!
+//! // Query the nonce that will be used for our seal operation with our prepared nonce
+//! let seal_nonce_bytes = Vec::from(seal_prepared_nonce.nonce().as_ref());
+//!
+//! // Use the prepared nonce and seal the plaintext
+//! seal_prepared_nonce.seal_in_place_append_tag(Aad::empty(), &mut in_out)?;
+//!
+//! // Create a OpeningKeyPreparedNonce which consumes a nonce from the underlying sequence
+//! let open_prepared_nonce = opening_key.prepare_nonce()?;
+//!
+//! // Query the nonce that will be used for our seal operation with our prepared nonce
+//! let open_nonce_bytes = Vec::from(open_prepared_nonce.nonce().as_ref());
+//!
+//! // Since we initialized the Counter32Builder the same between both builders the nonce here
+//! // will match the one from the opening key.
+//! assert_eq!(seal_nonce_bytes.as_slice(), open_nonce_bytes.as_slice());
+//!
+//! let plaintext = open_prepared_nonce.open_in_place(Aad::empty(), &mut in_out)?;
+//!
+//! assert_eq!(message.as_bytes(), plaintext);
+//! #   Ok(())
+//! # }
+//! ```
 
 use crate::{derive_debug_via_id, error::Unspecified, hkdf};
 use aead_ctx::AeadCtx;
-use core::{fmt::Debug, ops::RangeFrom};
+use core::{fmt::Debug, ops::RangeFrom, stringify};
+use paste::paste;
 
 mod aead_ctx;
 mod aes_gcm;
@@ -214,7 +281,6 @@ impl<N: NonceSequence> OpeningKey<N> {
     /// plaintext without the tag.
     ///
     /// Prefer [`RandomizedNonceKey::open_in_place`].
-    ///
     // # FIPS
     // Use this method with one of the following algorithms:
     // * `AES_128_GCM`
@@ -280,7 +346,6 @@ impl<N: NonceSequence> OpeningKey<N> {
     /// This reassembly be accomplished with three calls to `open_within()`.
     ///
     /// Prefer [`RandomizedNonceKey::open_in_place`].
-    ///
     // # FIPS
     // Use this method with one of the following algorithms:
     // * `AES_128_GCM`
@@ -307,6 +372,21 @@ impl<N: NonceSequence> OpeningKey<N> {
             ciphertext_and_tag,
         )
     }
+
+    /// Returns a `OpeningKeyPreparedNonce` containing the next computed `Nonce` consumed from `NonceSequence`.
+    ///
+    /// The encapsulated Nonce will be used **if and only if** either
+    /// [OpeningKeyPreparedNonce::open_in_place] or [OpeningKeyPreparedNonce::open_within]
+    /// are invoked. Dropping `OpeningKeyPreparedNonce` without invoking either method results in the nonce remaining
+    /// consumed and unused within the associated `NonceSequence`. Subsequent calls to [OpeningKey] methods will
+    /// always use a proceeding nonce from the `NonceSequence` regardless of whether
+    /// a `OpeningKeyPreparedNonce` is consumed or not.
+    ///
+    /// # Errors
+    /// `Unspecified` if there is a failure computing the nonce for the next operation, i.e. `NonceSequence` exhausted.
+    pub fn prepare_nonce(&mut self) -> Result<OpeningKeyPreparedNonce<'_, N>, Unspecified> {
+        OpeningKeyPreparedNonce::new(self)
+    }
 }
 
 /// An AEAD key for encrypting and signing ("sealing"), bound to a nonce
@@ -347,7 +427,6 @@ impl<N: NonceSequence> SealingKey<N> {
     /// Deprecated. Renamed to `seal_in_place_append_tag`.
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -378,7 +457,6 @@ impl<N: NonceSequence> SealingKey<N> {
     /// ```
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -413,7 +491,6 @@ impl<N: NonceSequence> SealingKey<N> {
     /// The tag will be `self.algorithm.tag_len()` bytes long.
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_separate_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -433,6 +510,149 @@ impl<N: NonceSequence> SealingKey<N> {
             .seal_in_place_separate_tag(Some(self.nonce_sequence.advance()?), aad.as_ref(), in_out)
             .map(|(_, tag)| tag)
     }
+
+    /// Returns a `SealingKeyPreparedNonce` containing the next computed `Nonce` consumed from `NonceSequence`.
+    ///
+    /// The encapsulated Nonce will be used **if and only if** either
+    /// [SealingKeyPreparedNonce::seal_in_place_append_tag] or [SealingKeyPreparedNonce::seal_in_place_separate_tag]
+    /// are invoked. Dropping `SealingKeyPreparedNonce` without invoking either method results in the nonce remaining
+    /// consumed and unused within the associated `NonceSequence`. Subsequent calls to [SealingKey] methods will
+    /// always use a proceeding nonce from the `NonceSequence` regardless of whether
+    /// a `SealingKeyPreparedNonce` is consumed or not.
+    ///
+    /// # Errors
+    /// `Unspecified` if there is a failure computing the nonce for the next operation, i.e. `NonceSequence` exhausted.
+    pub fn prepare_nonce(&mut self) -> Result<SealingKeyPreparedNonce<'_, N>, Unspecified> {
+        SealingKeyPreparedNonce::new(self)
+    }
+}
+
+macro_rules! nonce_seq_key_op_mut {
+    ($name:ident) => {
+        paste! {
+        /// A key operation with a precomputed nonce from a key's associated `NonceSequence`.
+        pub struct [<$name PreparedNonce>]<'a, N: NonceSequence> {
+            key: &'a mut $name<N>,
+            nonce: Nonce,
+        }
+
+        impl<'a, N: NonceSequence> [<$name PreparedNonce>]<'a, N> {
+            fn new(key: &'a mut $name<N>) -> Result<Self, Unspecified> {
+                let nonce = key.nonce_sequence.advance()?;
+                Ok(Self {
+                    key,
+                    nonce,
+                })
+            }
+        }
+
+        impl<N: NonceSequence> [<$name PreparedNonce>]<'_, N> {
+            /// Returns the prepared Nonce that is used for key methods invoked on [Self].
+            #[must_use]
+            pub fn nonce(&self) -> &Nonce {
+                &self.nonce
+            }
+        }
+
+        impl<N: NonceSequence> Debug for [<$name PreparedNonce>]<'_, N> {
+            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
+                f.debug_struct(stringify!([<$name PreparedNonce>])).finish_non_exhaustive()
+            }
+        }
+        }
+    };
+}
+
+nonce_seq_key_op_mut!(OpeningKey);
+nonce_seq_key_op_mut!(SealingKey);
+
+impl<N: NonceSequence> OpeningKeyPreparedNonce<'_, N> {
+    /// Authenticates and decrypts (“opens”) data in place.
+    ///
+    /// See [OpeningKey::open_in_place] for additional API information.
+    ///
+    /// # Errors
+    /// `error::Unspecified` when ciphertext is invalid. In this case, `in_out` may have been
+    /// overwritten in an unspecified way.
+    #[inline]
+    #[allow(clippy::needless_pass_by_value)]
+    pub fn open_in_place<A>(self, aad: Aad<A>, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified>
+    where
+        A: AsRef<[u8]>,
+    {
+        self.open_within(aad, in_out, 0..)
+    }
+
+    /// Authenticates and decrypts (“opens”) data in place, with a shift.
+    ///
+    /// See [OpeningKey::open_within] for additional API information.
+    ///
+    /// # Errors
+    /// `error::Unspecified` when ciphertext is invalid. In this case, `in_out` may have been
+    /// overwritten in an unspecified way.
+    #[inline]
+    #[allow(clippy::needless_pass_by_value)]
+    pub fn open_within<A>(
+        self,
+        aad: Aad<A>,
+        in_out: &mut [u8],
+        ciphertext_and_tag: RangeFrom<usize>,
+    ) -> Result<&mut [u8], Unspecified>
+    where
+        A: AsRef<[u8]>,
+    {
+        self.key
+            .key
+            .open_within(self.nonce, aad.as_ref(), in_out, ciphertext_and_tag)
+    }
+}
+
+impl<N: NonceSequence> SealingKeyPreparedNonce<'_, N> {
+    /// Encrypts and signs (“seals”) data in place, appending the tag to the
+    /// resulting ciphertext.
+    ///
+    /// See [SealingKey::seal_in_place_append_tag] for additional API information.
+    ///
+    /// # Errors
+    /// `error::Unspecified` when `nonce_sequence` cannot be advanced.
+    #[inline]
+    #[allow(clippy::needless_pass_by_value)]
+    pub fn seal_in_place_append_tag<A, InOut>(
+        self,
+        aad: Aad<A>,
+        in_out: &mut InOut,
+    ) -> Result<(), Unspecified>
+    where
+        A: AsRef<[u8]>,
+        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
+    {
+        self.key
+            .key
+            .seal_in_place_append_tag(Some(self.nonce), aad.as_ref(), in_out)
+            .map(|_| ())
+    }
+
+    /// Encrypts and signs (“seals”) data in place.
+    ///
+    /// See [`SealingKey::seal_in_place_separate_tag`] for additional API information.
+    ///
+    /// # Errors
+    /// `error::Unspecified` when `nonce_sequence` cannot be advanced.
+    #[inline]
+    #[allow(clippy::needless_pass_by_value)]
+    pub fn seal_in_place_separate_tag<A>(
+        self,
+        aad: Aad<A>,
+        in_out: &mut [u8],
+    ) -> Result<Tag, Unspecified>
+    where
+        A: AsRef<[u8]>,
+    {
+        self.key
+            .key
+            .seal_in_place_separate_tag(Some(self.nonce), aad.as_ref(), in_out)
+            .map(|(_, tag)| tag)
+    }
 }
 
 /// The additionally authenticated data (AAD) for an opening or sealing
@@ -478,7 +698,6 @@ impl hkdf::KeyType for &'static Algorithm {
 /// `NonceSequence` cannot reasonably be used.
 ///
 /// Prefer [`RandomizedNonceKey`] when practical.
-///
 // # FIPS
 // The following conditions must be met:
 // * `UnboundKey`'s algorithm is one of:
@@ -501,7 +720,6 @@ impl LessSafeKey {
     /// `nonce` must be unique for every use of the key to open data.
     ///
     /// Prefer [`RandomizedNonceKey::open_in_place`].
-    ///
     // # FIPS
     // Use this method with one of the following algorithms:
     // * `AES_128_GCM`
@@ -527,7 +745,6 @@ impl LessSafeKey {
     /// `nonce` must be unique for every use of the key to open data.
     ///
     /// Prefer [`RandomizedNonceKey::open_in_place`].
-    ///
     // # FIPS
     // Use this method with one of the following algorithms:
     // * `AES_128_GCM`
@@ -563,7 +780,6 @@ impl LessSafeKey {
     /// # Errors
     /// `error::Unspecified` when ciphertext is invalid. In this case, `out_plaintext` may
     /// have been overwritten in an unspecified way.
-    ///
     #[inline]
     #[allow(clippy::needless_pass_by_value)]
     pub fn open_separate_gather<A>(
@@ -584,7 +800,6 @@ impl LessSafeKey {
     /// Deprecated. Renamed to `seal_in_place_append_tag()`.
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -610,7 +825,6 @@ impl LessSafeKey {
     /// `nonce` must be unique for every use of the key to seal data.
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_append_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -639,7 +853,6 @@ impl LessSafeKey {
     /// `nonce` must be unique for every use of the key to seal data.
     ///
     /// Prefer [`RandomizedNonceKey::seal_in_place_separate_tag`].
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -674,7 +887,6 @@ impl LessSafeKey {
     /// The `extra_out_and_tag` length must be equal to the `extra_len` and `self.algorithm.tag_len()`.
     ///
     /// `nonce` must be unique for every use of the key to seal data.
-    ///
     // # FIPS
     // This method must not be used.
     //
@@ -786,6 +998,12 @@ impl AsRef<[u8]> for Tag {
     }
 }
 
+impl core::fmt::Debug for Tag {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_tuple("Tag").finish()
+    }
+}
+
 #[allow(dead_code)]
 const MAX_KEY_LEN: usize = 32;
 
@@ -797,6 +1015,8 @@ pub const MAX_TAG_LEN: usize = TAG_LEN;
 
 #[cfg(test)]
 mod tests {
+    use nonce_sequence::Counter32Builder;
+
     use super::*;
     use crate::{iv::FixedLength, test::from_hex};
 
@@ -853,4 +1073,26 @@ mod tests {
 
         assert_eq!(plaintext, in_out[..plaintext.len()]);
     }
+
+    #[test]
+    fn debug_prepared_nonce() {
+        let mut sk = SealingKey::new(
+            UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(),
+            Counter32Builder::new().build(),
+        );
+        let mut ok = OpeningKey::new(
+            UnboundKey::new(&AES_128_GCM, &[0u8; 16]).unwrap(),
+            Counter32Builder::new().build(),
+        );
+        let so = sk.prepare_nonce().unwrap();
+        let oo = ok.prepare_nonce().unwrap();
+        assert_eq!("SealingKeyPreparedNonce { .. }", format!("{so:?}"));
+        assert_eq!("OpeningKeyPreparedNonce { .. }", format!("{oo:?}"));
+    }
+
+    #[test]
+    fn debug_tag() {
+        let tag = Tag([0u8; MAX_TAG_LEN], MAX_TAG_LEN);
+        assert_eq!("Tag", format!("{tag:?}"));
+    }
 }
diff --git a/aws-lc-rs/tests/aead_test.rs b/aws-lc-rs/tests/aead_test.rs
index 3b521dd71b5..1423fff187d 100644
--- a/aws-lc-rs/tests/aead_test.rs
+++ b/aws-lc-rs/tests/aead_test.rs
@@ -3,9 +3,12 @@
 // Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 // SPDX-License-Identifier: Apache-2.0 OR ISC
 
+use aws_lc_rs::aead::nonce_sequence::Counter32Builder;
 use aws_lc_rs::{aead, error, test, test_file};
 
-use aws_lc_rs::aead::{Nonce, NONCE_LEN};
+use aws_lc_rs::aead::{
+    Aad, BoundKey, Nonce, OpeningKey, SealingKey, UnboundKey, AES_128_GCM, NONCE_LEN,
+};
 use core::ops::RangeFrom;
 
 #[test]
@@ -660,3 +663,90 @@ impl aead::NonceSequence for OneNonceSequence {
         self.0.take().ok_or(error::Unspecified)
     }
 }
+
+#[test]
+fn prepare_nonce() {
+    const KEY: &[u8] = &[
+        0x52, 0x05, 0x19, 0x7a, 0xcc, 0x88, 0xdb, 0x78, 0x39, 0x59, 0xbc, 0x03, 0xb8, 0x1d, 0x4a,
+        0x6c,
+    ];
+    const MESSAGE: &[u8] = &[
+        0x52, 0x61, 0x63, 0x63, 0x6f, 0x6f, 0x6e, 0x20, 0x4d, 0x69, 0x73, 0x63, 0x68, 0x69, 0x65,
+        0x66,
+    ];
+    const LIMIT: u32 = 10;
+
+    let mut sk = SealingKey::new(
+        UnboundKey::new(&AES_128_GCM, KEY).unwrap(),
+        Counter32Builder::new().limit(LIMIT).build(),
+    );
+    let mut ok = OpeningKey::new(
+        UnboundKey::new(&AES_128_GCM, KEY).unwrap(),
+        Counter32Builder::new().limit(LIMIT).build(),
+    );
+
+    let mut nonces: Vec<Vec<u8>> = vec![];
+
+    for _ in 0..(LIMIT / 2) {
+        let skpn = sk.prepare_nonce().unwrap();
+        let okpn = ok.prepare_nonce().unwrap();
+        let so_nonce = Vec::from(skpn.nonce().as_ref());
+        let oo_nonce = Vec::from(okpn.nonce().as_ref());
+
+        assert_eq!(so_nonce.as_slice(), oo_nonce.as_slice());
+        assert!(!nonces.contains(&so_nonce));
+        nonces.push(so_nonce);
+        nonces.push(oo_nonce);
+
+        let mut message: Vec<u8> = vec![];
+        message.extend_from_slice(MESSAGE);
+
+        skpn.seal_in_place_append_tag(Aad::empty(), &mut message)
+            .unwrap();
+        assert_ne!(MESSAGE, message.as_slice());
+
+        let message = okpn.open_in_place(Aad::empty(), &mut message).unwrap();
+        assert_eq!(MESSAGE, message);
+
+        let so = sk.prepare_nonce().unwrap();
+        let oo = ok.prepare_nonce().unwrap();
+        let so_nonce = Vec::from(so.nonce().as_ref());
+        let oo_nonce = Vec::from(oo.nonce().as_ref());
+
+        assert_eq!(so_nonce.as_slice(), oo_nonce.as_slice());
+        assert!(!nonces.contains(&so_nonce));
+        nonces.push(so_nonce);
+        nonces.push(oo_nonce);
+
+        let mut message: Vec<u8> = vec![];
+        message.extend_from_slice(MESSAGE);
+
+        let tag = so
+            .seal_in_place_separate_tag(Aad::empty(), &mut message)
+            .unwrap();
+        assert_ne!(MESSAGE, message.as_slice());
+        message.extend_from_slice(tag.as_ref());
+
+        let message = oo.open_within(Aad::empty(), &mut message, 0..).unwrap();
+        assert_eq!(MESSAGE, message);
+    }
+
+    let nonce_chunks = nonces.chunks_exact(2);
+    assert_eq!(0, nonce_chunks.remainder().len());
+    for chunk in nonce_chunks {
+        assert_eq!(chunk[0].as_slice(), chunk[1].as_slice());
+    }
+
+    let mut message: Vec<u8> = vec![];
+    message.extend_from_slice(MESSAGE);
+
+    // Subsequent usage should fail now since the sequence is exhausted in each key.
+    sk.prepare_nonce().expect_err("sequence limit reached");
+    ok.prepare_nonce().expect_err("sequence limit reached");
+    sk.seal_in_place_append_tag(Aad::empty(), &mut message)
+        .expect_err("sequence limit reached");
+    sk.seal_in_place_separate_tag(Aad::empty(), &mut message)
+        .expect_err("sequence limit reached");
+    ok.open_in_place(Aad::empty(), &mut message)
+        .expect_err("sequence limit reached");
+}