Skip to main content

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