Pretty Printing

- 3 mins read

Series: cryptopals

In this series of articles, I am going to attempt to solve the cryptopals crypto challenges. These challenges provide a fun way to learn about cryptography. As mentioned on their website, the challenges are “derived from weaknesses in real-world systems and modern cryptographic constructions”. I will focus mainly on solving them in the Rust programming language, though I do not exclude the possibility of using another language at one point or another in this series.

Why Rust?

Rust seems fun, and I do have some projects in mind I want to use it for in the future. I do believe it is easier to learn hands-on, though, this series will not be a programming tutorial. Feel free to follow along in any programming language you are proficient in or want to learn.

Convert hex to base64

This is important, because raw bytes allow for precise manipulation at the byte level. When working with encoded strings, there’s a layer of abstraction that can obscure the actual bytes being manipulated. This abstraction can lead to unintended consequences or errors, especially in cryptographic operations where precision is vital.

Always operate on raw bytes, never on encoded strings. Only use hex and base64 for pretty-printing.

In this challenge, the string:

49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d

Should produce:

SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

Receive input string - encode it - display it.

use std::{
    env,
    error::Error,
};
use base64::{engine::general_purpose::STANDARD, Engine as _};

fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage:\n\thextobase64 <hex string>");
        return Ok(());
    }
    let hexstr = &args[1];
    println!("{}", STANDARD.encode(&hexstr));
    Ok(())
}

And done! Except I committed a mistake:

> hextobase64 4927...f6f6d
> NDky...2ZjZk

Instead of encoding the 0x49 0x27 ... 0x6d hex values, the code encodes each char separately, so ‘49’ ends up being 0x34 0x39. For reference: ascii-table

Shortly, the string we have to encode is a hexadecimal representation of binary data. Each pair of characters in this string represents a byte in hexadecimal notation.

The initial string is parsed and bytes are pushed to a u8 vector. The revised code looks like this:

use std::{
    env,
    error::Error,
};
use base64::{engine::general_purpose::STANDARD, Engine as _};

fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage:\n\thextobase64 <hex string>");
        return Ok(());
    }
    let hexstr = &args[1];

    // if the length of hexstr is not even, return error
    if !hexstr.len() % 2 == 0 {
        return Err("not a valid hex encoded string".into());
    }
    // will store the hex values in a vector of u8 (e.g. 0x49, 0x27 -> 73, 39)
    let mut bytes: Vec<u8> = Vec::new();    
    // parse the input string, push hex bytes to vector
    for i in (0..hexstr.len()).step_by(2) {
        let byte = u8::from_str_radix(&hexstr[i..][..2], 16).unwrap();
        bytes.push(byte);
    }
    println!("{}", STANDARD.encode(&bytes));
    Ok(())
}

In hexadecimal encoding, each hexadecimal digit represents four binary digits (bits). Therefore, each byte (8 bits) is represented by two hexadecimal digits. If the input string doesn’t have an even number of characters, it implies an incomplete byte, which could lead to errors during processing.

> hextobase64 49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d
> SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t

Get Involved

I think knowledge should be shared and discussions encouraged. So, don’t hesitate to ask questions, or suggest topics you’d like me to cover in future posts.

Stay Connected

You can contact me on J6597@tutanota.com


comments powered by Disqus