api/device_info: add password_stretching_algo
What changed, and why it matters
This commit adds a new read-only field to the BitBox02's device information API that tells the connected app which password-stretching algorithm the device uses. It does not change how passwords are stretched or how they are verified; it only exposes existing internal information to the host. There is no indication this is a security fix or that it introduces a vulnerability.
No security action required. Treat as a normal feature/API addition. If reviewing for release readiness, verify the new field is correctly gated by firmware version in downstream consumers and that the protobuf wire compatibility is acceptable.
Security signals we found
Read-only API field addition with no authorization or input-handling changes
No modification to password stretching, keystore, or secure-chip code paths
No bounds, format, or memory-safety changes observed
No vendor disclosure of security relevance in commit message or diff
Evidence from the diff
The change extends the DeviceInfoResponse protobuf with password_stretching_algo (string, field 8). The firmware populates it from memory::get_encrypted_seed_and_hmac() for seeded devices, or from keystore::default_password_stretch_algo() for unseeded devices, mapping the internal enum values V0/V1 to the strings “V0”/”V1”. Python generated protobuf code and the high-level Python client are updated to surface the new field for firmware >= v9.25.0. The securechip_model comment is also updated to include OPTIGA_TRUST_M_V3. No logic for password stretching, verification, or keystore operations is modified.
Changed components
messages/bitbox02_system.protopy/bitbox02/bitbox02/bitbox02.pypy/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pypy/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyisrc/rust/bitbox02-rust/src/hww/api/device_info.rssrc/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rsInspect captured patch +56 / −23
diff --git a/messages/bitbox02_system.proto b/messages/bitbox02_system.proto
index a4d5975..463e689 100644
--- a/messages/bitbox02_system.proto
+++ b/messages/bitbox02_system.proto
@@ -27,10 +27,13 @@ message DeviceInfoResponse {
string version = 3;
bool mnemonic_passphrase_enabled = 4;
uint32 monotonic_increments_remaining = 5;
- // From v9.6.0: "ATECC608A" or "ATECC608B".
+ // From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3".
string securechip_model = 6;
// Only present in Bluetooth-enabled devices.
optional Bluetooth bluetooth = 7;
+ // From v9.25.0. This together with `securechip_model` determines the password stretching
+ // algorithm.
+ string password_stretching_algo = 8;
}
message InsertRemoveSDCardRequest {
@@ -56,4 +59,4 @@ message SetPasswordRequest {
}
message ChangePasswordRequest{
-}
\ No newline at end of file
+}
diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
index 48dd9f9..ebe3f7f 100644
--- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py
+++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
@@ -171,6 +171,8 @@ class BitBox02(BitBoxCommonAPI):
}
else:
result["bluetooth"] = None
+ if self.version >= semver.VersionInfo(9, 25, 0):
+ result["password_stretching_algo"] = response.device_info.password_stretching_algo
return result
diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py
index 4e07d70..95a806e 100644
--- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py
+++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xd8\x02\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\x12J\n\tbluetooth\x18\x07 \x01(\x0b\x32\x32.shiftcrypto.bitbox02.DeviceInfoResponse.BluetoothH\x00\x88\x01\x01\x1aM\n\tBluetooth\x12\x15\n\rfirmware_hash\x18\x01 \x01(\x0c\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x42\x0c\n\n_bluetooth\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xfa\x02\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\x12J\n\tbluetooth\x18\x07 \x01(\x0b\x32\x32.shiftcrypto.bitbox02.DeviceInfoResponse.BluetoothH\x00\x88\x01\x01\x12 \n\x18password_stretching_algo\x18\x08 \x01(\t\x1aM\n\tBluetooth\x12\x15\n\rfirmware_hash\x18\x01 \x01(\x0c\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x42\x0c\n\n_bluetooth\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'bitbox02_system_pb2', globals())
@@ -27,21 +27,21 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_DEVICEINFOREQUEST._serialized_start=110
_DEVICEINFOREQUEST._serialized_end=129
_DEVICEINFORESPONSE._serialized_start=132
- _DEVICEINFORESPONSE._serialized_end=476
- _DEVICEINFORESPONSE_BLUETOOTH._serialized_start=385
- _DEVICEINFORESPONSE_BLUETOOTH._serialized_end=462
- _INSERTREMOVESDCARDREQUEST._serialized_start=479
- _INSERTREMOVESDCARDREQUEST._serialized_end=634
- _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_start=586
- _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_end=634
- _RESETREQUEST._serialized_start=636
- _RESETREQUEST._serialized_end=650
- _SETDEVICELANGUAGEREQUEST._serialized_start=652
- _SETDEVICELANGUAGEREQUEST._serialized_end=696
- _SETDEVICENAMEREQUEST._serialized_start=698
- _SETDEVICENAMEREQUEST._serialized_end=734
- _SETPASSWORDREQUEST._serialized_start=736
- _SETPASSWORDREQUEST._serialized_end=773
- _CHANGEPASSWORDREQUEST._serialized_start=775
- _CHANGEPASSWORDREQUEST._serialized_end=798
+ _DEVICEINFORESPONSE._serialized_end=510
+ _DEVICEINFORESPONSE_BLUETOOTH._serialized_start=419
+ _DEVICEINFORESPONSE_BLUETOOTH._serialized_end=496
+ _INSERTREMOVESDCARDREQUEST._serialized_start=513
+ _INSERTREMOVESDCARDREQUEST._serialized_end=668
+ _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_start=620
+ _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_end=668
+ _RESETREQUEST._serialized_start=670
+ _RESETREQUEST._serialized_end=684
+ _SETDEVICELANGUAGEREQUEST._serialized_start=686
+ _SETDEVICELANGUAGEREQUEST._serialized_end=730
+ _SETDEVICENAMEREQUEST._serialized_start=732
+ _SETDEVICENAMEREQUEST._serialized_end=768
+ _SETPASSWORDREQUEST._serialized_start=770
+ _SETPASSWORDREQUEST._serialized_end=807
+ _CHANGEPASSWORDREQUEST._serialized_start=809
+ _CHANGEPASSWORDREQUEST._serialized_end=832
# @@protoc_insertion_point(module_scope)
diff --git a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi
index bf18e92..f68c4a8 100644
--- a/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi
+++ b/py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi
@@ -87,13 +87,18 @@ class DeviceInfoResponse(google.protobuf.message.Message):
MONOTONIC_INCREMENTS_REMAINING_FIELD_NUMBER: builtins.int
SECURECHIP_MODEL_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
+ PASSWORD_STRETCHING_ALGO_FIELD_NUMBER: builtins.int
name: builtins.str
initialized: builtins.bool
version: builtins.str
mnemonic_passphrase_enabled: builtins.bool
monotonic_increments_remaining: builtins.int
securechip_model: builtins.str
- """From v9.6.0: "ATECC608A" or "ATECC608B"."""
+ """From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3"."""
+ password_stretching_algo: builtins.str
+ """From v9.25.0. This together with `securechip_model` determines the password stretching
+ algorithm.
+ """
@property
def bluetooth(self) -> global___DeviceInfoResponse.Bluetooth:
"""Only present in Bluetooth-enabled devices."""
@@ -108,9 +113,10 @@ class DeviceInfoResponse(google.protobuf.message.Message):
monotonic_increments_remaining: builtins.int = ...,
securechip_model: builtins.str = ...,
bluetooth: global___DeviceInfoResponse.Bluetooth | None = ...,
+ password_stretching_algo: builtins.str = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "bluetooth", b"bluetooth"]) -> builtins.bool: ...
- def ClearField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "bluetooth", b"bluetooth", "initialized", b"initialized", "mnemonic_passphrase_enabled", b"mnemonic_passphrase_enabled", "monotonic_increments_remaining", b"monotonic_increments_remaining", "name", b"name", "securechip_model", b"securechip_model", "version", b"version"]) -> None: ...
+ def ClearField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "bluetooth", b"bluetooth", "initialized", b"initialized", "mnemonic_passphrase_enabled", b"mnemonic_passphrase_enabled", "monotonic_increments_remaining", b"monotonic_increments_remaining", "name", b"name", "password_stretching_algo", b"password_stretching_algo", "securechip_model", b"securechip_model", "version", b"version"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_bluetooth", b"_bluetooth"]) -> typing.Literal["bluetooth"] | None: ...
global___DeviceInfoResponse = DeviceInfoResponse
diff --git a/src/rust/bitbox02-rust/src/hww/api/device_info.rs b/src/rust/bitbox02-rust/src/hww/api/device_info.rs
index eb2de82..e068be2 100644
--- a/src/rust/bitbox02-rust/src/hww/api/device_info.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/device_info.rs
@@ -20,6 +20,20 @@ pub fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error> {
}
memory::Platform::BitBox02 => None,
};
+ // We display the stretching algo that is used on a seeded device, or the algo that would be
+ // used for a new seed on an unseeded device.
+ let password_stretching_algo = if hal.memory().is_seeded() {
+ let (_, password_stretching_algo) = hal
+ .memory()
+ .get_encrypted_seed_and_hmac()
+ .map_err(|_| Error::Memory)?;
+ password_stretching_algo
+ } else {
+ crate::keystore::default_password_stretch_algo(hal).map_err(|err| match err {
+ crate::keystore::Error::Memory => Error::Memory,
+ _ => Error::Generic,
+ })?
+ };
Ok(Response::DeviceInfo(pb::DeviceInfoResponse {
name: hal.memory().get_device_name(),
initialized: hal.memory().is_initialized(),
@@ -32,5 +46,9 @@ pub fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error> {
securechip::Model::OPTIGA_TRUST_M_V3 => "OPTIGA_TRUST_M_V3".into(),
},
bluetooth,
+ password_stretching_algo: match password_stretching_algo {
+ bitbox02::memory::PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V0 => "V0".into(),
+ bitbox02::memory::PasswordStretchAlgo::MEMORY_PASSWORD_STRETCH_ALGO_V1 => "V1".into(),
+ },
}))
}
diff --git a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
index 88c0efa..28612df 100644
--- a/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
+++ b/src/rust/bitbox02-rust/src/shiftcrypto.bitbox02.rs
@@ -129,12 +129,16 @@ pub struct DeviceInfoResponse {
pub mnemonic_passphrase_enabled: bool,
#[prost(uint32, tag = "5")]
pub monotonic_increments_remaining: u32,
- /// From v9.6.0: "ATECC608A" or "ATECC608B".
+ /// From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3".
#[prost(string, tag = "6")]
pub securechip_model: ::prost::alloc::string::String,
/// Only present in Bluetooth-enabled devices.
#[prost(message, optional, tag = "7")]
pub bluetooth: ::core::option::Option<device_info_response::Bluetooth>,
+ /// From v9.25.0. This together with `securechip_model` determines the password stretching
+ /// algorithm.
+ #[prost(string, tag = "8")]
+ pub password_stretching_algo: ::prost::alloc::string::String,
}
/// Nested message and enum types in `DeviceInfoResponse`.
pub mod device_info_response {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.