Skip to content

[SerializedPersonalData]

The [SerializedPersonalData] attribute marks a non-string property (such as DateTime, int, decimal, or other value types) as personal data that requires binary serialization before encryption. The encrypted bytes are stored in a separate companion byte[] property.

Why a Separate Attribute?

[PersonalData] works by replacing the property's string value with Base64-encoded ciphertext. This does not work for non-string types like DateTime or int because the ciphertext cannot be stored in a typed property. [SerializedPersonalData] solves this by:

  1. Serializing the value to a byte[] using a built-in binary serializer.
  2. Encrypting the bytes with AES-256-GCM.
  3. Storing the encrypted result in a companion byte[]? property.
  4. Leaving the original property at its default value after encryption (for storage optimization, the actual value is in the encrypted bytes).

Basic Usage

cs
public class EmployeeRecord
{
    [DataSubjectId]
    public Guid EmployeeId { get; set; }

    [PersonalData]
    public string Name { get; set; } = "";

    [SerializedPersonalData]
    public DateTime DateOfBirth { get; set; }

    /// <summary>
    /// Companion field — stores the encrypted bytes of DateOfBirth.
    /// Follows the default naming convention: {PropertyName}Encrypted.
    /// </summary>
    public byte[]? DateOfBirthEncrypted { get; set; }

    [SerializedPersonalData]
    public int SocialSecurityNumber { get; set; }

    /// <summary>
    /// Companion field for SocialSecurityNumber.
    /// </summary>
    public byte[]? SocialSecurityNumberEncrypted { get; set; }
}
anchor

Each [SerializedPersonalData] field requires a companion byte[]? property following the naming convention {PropertyName}Encrypted:

Source PropertyCompanion Property
DateOfBirthDateOfBirthEncrypted
SocialSecurityNumberSocialSecurityNumberEncrypted

During EncryptAsync:

  • The value of DateOfBirth is serialized to bytes, encrypted, and stored in DateOfBirthEncrypted.
  • The companion field holds the ciphertext. The original property's value can be zeroed or left as-is in the stored entity.

During DecryptAsync:

  • The bytes in DateOfBirthEncrypted are decrypted, deserialized, and written back to DateOfBirth.
  • The companion field is set to null after successful decryption.

Custom Companion Field Name

If your companion property does not follow the default naming convention, use the EncryptedFieldName property:

cs
public class LegacyRecord
{
    [DataSubjectId]
    public Guid Id { get; set; }

    [SerializedPersonalData(EncryptedFieldName = "CustomBytes")]
    public DateTime HireDate { get; set; }

    /// <summary>
    /// Custom-named companion field specified via EncryptedFieldName.
    /// </summary>
    public byte[]? CustomBytes { get; set; }
}
anchor

Here, the encrypted bytes for HireDate are stored in CustomBytes instead of the default HireDateEncrypted.

Properties Reference

PropertyTypeDefaultDescription
Groupstring?nullLinks this field to a specific [DataSubjectId] group. Same as [PersonalData].Group.
EncryptedFieldNamestring?nullName of the companion byte[]? property. If not set, defaults to {PropertyName}Encrypted.

Companion Field Convention

The companion field must be:

  • A public instance property.
  • Of type byte[]? (nullable byte array).
  • On the same class as the [SerializedPersonalData] property.

If the companion property cannot be found, Tayra logs a warning and skips encryption for that field.

Database Mapping

When using Entity Framework Core or Marten, make sure the companion byte[]? property is mapped to a column in your database. The original typed property may also need to be mapped if you want it available after decryption. Some developers mark the original property as a computed/transient property and only persist the encrypted bytes.

Crypto-Shredding Behavior

After the encryption key is deleted:

  • DecryptAsync sets the original property to its type's default value (default(DateTime), 0 for int, etc.).
  • The companion byte[]? property is set to null.

There is no custom MaskValue property on [SerializedPersonalData] -- the type's default value is always used.

Supported Types

The built-in binary serializer supports these types:

TypeSerialization
int4-byte little-endian
long8-byte little-endian
double8-byte IEEE 754
decimal16-byte
DateTime8-byte ticks (Int64)
DateTimeOffset8-byte ticks + 2-byte offset
Guid16-byte
bool1-byte

For other types, consider converting to string and using [PersonalData] instead.

See Also