bouncycastle_hex/lib.rs
1//! Good old fashioned hex encoder and decoder.
2//!
3//! This one is implemented using constant-time operations in the conversions
4//! from Strings to byte values, so it is safe to use on cryptographic secret values.
5//!
6//! It should just work as expected:
7//! encode takes any bytes-like rust type and returns a String,
8//! decode takes a String (which can be in any bytes-like container) and returns a `Vec<u8>`.
9//!
10//! Moreover, the API of this crate is intended to mirror that of the public `hex` crate,
11//! so you should generally be able to swap `use hex` for `use bouncycastle_hex` and all the function
12//! calls and behaviours should work as expected.
13//!
14//! ```
15//! use bouncycastle_hex as hex;
16//!
17//! let out = hex::encode(b"\x00\x01\x02\x03"); // "00010203"
18//! let out = hex::encode(&[0x00, 0x01, 0x02, 0x03]); // "00010203"
19//! let out = hex::encode(vec![0x00, 0x01, 0x02, 0x03]); // "00010203"
20//!
21//! let out = hex::decode("00010203").unwrap(); // [0x00, 0x01, 0x02, 0x03]
22//! let out = hex::decode(b"00010203").unwrap(); // [0x00, 0x01, 0x02, 0x03]
23//! ```
24//!
25//! The decoder ignores whitespace and "\x".
26
27#![forbid(unsafe_code)]
28#![forbid(missing_docs)]
29
30use bouncycastle_utils::ct::Condition;
31
32/// Return type for errors relating to Hex encoding and decoding.
33#[derive(Debug)]
34pub enum HexError {
35 /// Invalid hex character encountered at the given index.
36 InvalidHexCharacter(usize),
37 /// Since hex encodes each byte as two characters, the input must have an even length.
38 OddLengthInput,
39 ///
40 InsufficientOutputBufferSize,
41}
42
43/// One-shot encode from bytes to a hex-encoded string using a constant-time implementation.
44pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
45 let mut out = vec![0u8; input.as_ref().len() * 2];
46 encode_out(input.as_ref(), &mut out).unwrap();
47
48 String::from_utf8(out).unwrap()
49}
50
51/// expects an output array which is at least input.len() / 2 in size.
52/// Returns the number of bytes written.
53pub fn encode_out<T: AsRef<[u8]>>(input: T, out: &mut [u8]) -> Result<usize, HexError> {
54 let inref = input.as_ref();
55 if out.len() < inref.len() * 2 {
56 return Err(HexError::InsufficientOutputBufferSize);
57 }
58
59 out.fill(0);
60
61 for i in 0..inref.len() {
62 out[2 * i] = ct_word_to_hex(inref[i] >> 4);
63 out[2 * i + 1] = ct_word_to_hex(inref[i] & 0x0F);
64 }
65 return Ok(inref.len() * 2);
66
67 /// Expects a 4-bit word in the least significant bits.
68 fn ct_word_to_hex(mut c: u8) -> u8 {
69 // Make sure there's nothing in the top bits
70 c &= 0x0F;
71
72 // let in_09 = Condition::<i64>::is_within_range(c as i64, 0, 9);
73 let in_af = Condition::<i64>::is_within_range(c as i64, 10, 15);
74
75 // TODO: redo this once we have ct::u8 implemented
76 // The i64 is wasteful
77
78 let c_09: i64 = '0' as i64 + (c as i64);
79 let c_az: i64 = 'a' as i64 + (c as i64 - 10);
80
81 let mut ret: i64 = c_09 as i64;
82 ret = in_af.select(c_az as i64, ret);
83 ret as u8
84 }
85}
86
87/// One-shot decode from a hex string to a bytes using a constant-time implementation.
88/// ignores whitespace and \x
89pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, HexError> {
90 let inref = input.as_ref();
91 let mut out: Vec<u8> = vec![0u8; inref.len() / 2];
92 let bytes_written = decode_out(inref, &mut out)?;
93 out.truncate(bytes_written);
94 Ok(out)
95}
96
97/// expects an output array which is at least input.len() / 2 in size.
98/// Returns the number of bytes written.
99pub fn decode_out<T: AsRef<[u8]>>(input: T, out: &mut [u8]) -> Result<usize, HexError> {
100 let inref = input.as_ref();
101 if out.len() < inref.len() / 2 {
102 return Err(HexError::InsufficientOutputBufferSize);
103 }
104
105 out.fill(0);
106
107 let mut b = 0u8;
108 let mut b_i = 0u8;
109 let mut out_i = 0_usize;
110 let mut i = 0_usize;
111 while i < inref.len() {
112 let c = inref[i];
113
114 // first check for whitespace and string null terminators, \x and invalid characters,
115 // which unfortunately cannot be done fully constant-time.
116 match c {
117 b' ' | b'\t' | b'\n' | b'\r' | 0 => {
118 i += 1;
119 continue;
120 }
121 b'\\' => {
122 if inref[i + 1] == b'x' {
123 i += 2;
124 continue;
125 }
126 }
127 _ => {}
128 }
129
130 // parse two hex digits to form one output byte;
131 // the first one is the upper 4 bits.
132 b |= match ct_hex_to_word(c) {
133 0xFF => return Err(HexError::InvalidHexCharacter(i)),
134 c => c,
135 } << (4 * (1 - b_i));
136
137 if b_i == 1 {
138 out[out_i] = b;
139 out_i += 1;
140 b = 0;
141 b_i = 0;
142 } else {
143 b_i = 1;
144 }
145 i += 1;
146 }
147 // if b_i != 0, then we have an un-processed word in the buffer.
148 if b_i != 0 {
149 return Err(HexError::OddLengthInput);
150 }
151
152 return Ok(out_i);
153
154 fn ct_hex_to_word(b: u8) -> u8 {
155 let in_09 = Condition::<i64>::is_within_range(b as i64, 48, 57);
156 let in_af = Condition::<i64>::is_within_range(b as i64, 97, 102);
157 #[allow(non_snake_case)]
158 let in_AF = Condition::<i64>::is_within_range(b as i64, 65, 70);
159
160 // TODO: redo this once we have ct::u8 implemented
161 // The i64 is wasteful
162
163 let c_09: i64 = b as i64 - ('0' as i64);
164 #[allow(non_snake_case)]
165 let c_AF: i64 = b as i64 - ('A' as i64) + 10;
166 let c_af: i64 = b as i64 - ('a' as i64) + 10;
167
168 let mut ret: i64 = 0xFFi64;
169
170 ret = in_09.select(c_09, ret);
171 ret = in_AF.select(c_AF, ret);
172 ret = in_af.select(c_af, ret);
173
174 ret as u8
175 }
176}