rpkg_rs/encryption/
xtea.rs1use crate::encryption::xtea::XteaError::InvalidInput;
2use byteorder::{LittleEndian, WriteBytesExt};
3use extended_tea::XTEA;
4use std::io::Cursor;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
9pub enum XteaError {
10 #[error("Text encoding error: {0}")]
11 TextEncodingError(std::string::FromUtf8Error),
12
13 #[error("An error occurred while trying to decrypt the file: {:?}", _0)]
14 CipherError(std::io::Error),
15
16 #[error("Unexpected input: {:?}", _0)]
17 InvalidInput(String),
18
19 #[error("Xtea encoding error: {0}")]
20 XteaEncodingError(std::io::Error),
21}
22
23pub struct Xtea;
25
26impl Xtea {
27 pub const DEFAULT_KEY: [u32; 4] = [0x30f95282, 0x1f48c419, 0x295f8548, 0x2a78366d];
29
30 pub const LOCR_KEY: [u32; 4] = [0x53527737, 0x7506499E, 0xBD39AEE3, 0xA59E7268];
32
33 const DEFAULT_ENCRYPTED_HEADER: [u8; 0x10] = [
35 0x22, 0x3d, 0x6f, 0x9a, 0xb3, 0xf8, 0xfe, 0xb6, 0x61, 0xd9, 0xcc, 0x1c, 0x62, 0xde, 0x83,
36 0x41,
37 ];
38
39 pub fn is_encrypted_text_file(input_buffer: &[u8]) -> bool {
42 input_buffer.starts_with(&Self::DEFAULT_ENCRYPTED_HEADER)
43 }
44
45 pub fn decrypt_text_file(input_buffer: &[u8]) -> Result<String, XteaError> {
47 let payload_start = Self::DEFAULT_ENCRYPTED_HEADER.len() + 4;
48
49 if input_buffer.len() < payload_start {
50 return Err(InvalidInput("Input too short".to_string()));
51 }
52
53 if !input_buffer.starts_with(&Self::DEFAULT_ENCRYPTED_HEADER) {
54 return Err(InvalidInput("Header mismatch".to_string()));
55 }
56 let checksum = &input_buffer[payload_start - 4..payload_start];
57 let input = &input_buffer[payload_start..];
58
59 if input.len() % 8 != 0 {
60 return Err(InvalidInput(
61 "Input must be of a length divisible by 8".to_string(),
62 ));
63 }
64
65 let xtea = XTEA::new(&Self::DEFAULT_KEY);
66 let mut out_buffer = vec![0u8; input.len()];
67
68 let mut input_reader = Cursor::new(input);
69 let mut ouput_writer = Cursor::new(&mut out_buffer);
70
71 xtea.decipher_stream::<LittleEndian, _, _>(&mut input_reader, &mut ouput_writer)
72 .map_err(XteaError::CipherError)?;
73
74 let output = String::from_utf8(ouput_writer.get_mut().to_owned())
75 .map_err(XteaError::TextEncodingError)?;
76
77 let result_checksum =
78 crc32fast::hash(output.trim_end_matches('\0').as_bytes()).to_le_bytes();
79 match result_checksum == checksum {
80 true => Ok(output),
81 false => Err(InvalidInput("CRC checksum mismatched!".to_string())),
82 }
83 }
84
85 pub fn decrypt_string(input_buffer: &[u8], key: &[u32; 4]) -> Result<String, XteaError> {
87 let input = &input_buffer;
88
89 if input.len() % 8 != 0 {
90 return Err(InvalidInput(
91 "Input must be of a length divisible by 8".to_string(),
92 ));
93 }
94
95 let xtea = XTEA::new(key);
96 let mut out_buffer = vec![0u8; input.len()];
97
98 let mut input_reader = Cursor::new(input);
99 let mut ouput_writer = Cursor::new(&mut out_buffer);
100
101 xtea.decipher_stream::<LittleEndian, _, _>(&mut input_reader, &mut ouput_writer)
102 .map_err(XteaError::CipherError)?;
103
104 String::from_utf8(ouput_writer.get_mut().to_owned()).map_err(XteaError::TextEncodingError)
105 }
106
107 pub fn encrypt_text_file(input_string: String) -> Result<Vec<u8>, XteaError> {
108 let mut input_buffer = input_string.trim_end_matches('\0').as_bytes().to_vec();
110 let checksum = crc32fast::hash(&input_buffer);
111
112 let padding = 8 - (input_buffer.len() % 8);
113 if padding < 8 {
114 input_buffer.extend(vec![0u8; padding]);
115 }
116 let mut out_buffer = vec![0u8; input_buffer.len()];
117 let xtea = XTEA::new(&Self::DEFAULT_KEY);
118
119 let mut input_reader = Cursor::new(&input_buffer);
120 let mut output_writer = Cursor::new(&mut out_buffer);
121
122 xtea.encipher_stream::<LittleEndian, _, _>(&mut input_reader, &mut output_writer)
123 .map_err(XteaError::CipherError)?;
124
125 let mut final_buffer = Vec::new();
126 final_buffer.extend_from_slice(&Self::DEFAULT_ENCRYPTED_HEADER);
127
128 final_buffer
129 .write_u32::<LittleEndian>(checksum)
130 .map_err(XteaError::XteaEncodingError)?;
131
132 final_buffer.extend_from_slice(&out_buffer);
133
134 Ok(final_buffer)
135 }
136
137 pub fn encrypt_string(input_string: String, key: &[u32; 4]) -> Result<Vec<u8>, XteaError> {
138 let mut input_buffer = input_string.into_bytes();
139
140 let padding = 8 - (input_buffer.len() % 8);
142 if padding < 8 {
143 input_buffer.extend(vec![0u8; padding]);
144 }
145
146 let mut out_buffer = vec![0u8; input_buffer.len()];
147 let xtea = XTEA::new(key);
148
149 let mut input_reader = Cursor::new(&input_buffer);
150 let mut output_writer = Cursor::new(&mut out_buffer);
151
152 xtea.encipher_stream::<LittleEndian, _, _>(&mut input_reader, &mut output_writer)
153 .map_err(XteaError::CipherError)?;
154
155 Ok(out_buffer)
156 }
157}