md5_crypt.c
Go to the documentation of this file.
1 /**
2  * @file md5_crypt.c
3  * @brief Unix crypt using MD5
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2025 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneCRYPTO Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.5.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL CRYPTO_TRACE_LEVEL
33 
34 //Dependencies
35 #include "core/crypto.h"
36 #include "kdf/md5_crypt.h"
37 #include "hash/md5.h"
38 
39 //Check crypto library configuration
40 #if (MD5_CRYPT_SUPPORT == ENABLED)
41 
42 //Base64 encoding table
43 static const char_t base64EncTable[64] =
44 {
45  '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
46  'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
47  'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
48  'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
49 };
50 
51 
52 /**
53  * @brief MD5-crypt algorithm
54  * @param[in] password NULL-terminated password
55  * @param[in] salt NULL-terminated salt string
56  * @param[out] output Output string
57  * @param[out] outputLen Length of the output string (optional parameter)
58  * @return Error code
59  **/
60 
61 error_t md5Crypt(const char_t *password, const char_t *salt, char_t *output,
62  size_t *outputLen)
63 {
64  uint_t i;
65  size_t j;
66  size_t n;
67  size_t saltLen;
68  size_t passwordLen;
69  uint8_t digest[MD5_DIGEST_SIZE];
70 #if (CRYPTO_STATIC_MEM_SUPPORT == DISABLED)
71  Md5Context *md5Context;
72 #else
73  Md5Context md5Context[2];
74 #endif
75 
76  //Check parameters
77  if(password == NULL || salt == NULL || output == NULL)
79 
80  //Skip the salt prefix, if any
81  if(osStrncmp(salt, "$1$", 3) == 0)
82  {
83  salt += 3;
84  }
85 
86  //Retrieve the length of the salt string
87  saltLen = osStrlen(salt);
88  //The salt string can be up to 16 characters
89  saltLen = MIN(saltLen, MD5_CRYPT_MAX_SALT_LEN);
90 
91  //Retrieve the length of the password string
92  passwordLen = osStrlen(password);
93 
94 #if (CRYPTO_STATIC_MEM_SUPPORT == DISABLED)
95  //Allocate a memory buffer to hold the hash contexts
96  md5Context = cryptoAllocMem(2 * sizeof(Md5Context));
97  //Failed to allocate memory?
98  if(md5Context == NULL)
99  return ERROR_OUT_OF_MEMORY;
100 #endif
101 
102  //Start digest A
103  md5Init(&md5Context[0]);
104  //The password string is added to digest A
105  md5Update(&md5Context[0], password, passwordLen);
106  //The salt prefix is added to digest A
107  md5Update(&md5Context[0], "$1$", 3);
108  //The salt string is added to digest A
109  md5Update(&md5Context[0], salt, saltLen);
110 
111  //Start digest B
112  md5Init(&md5Context[1]);
113  //Add the password to digest B
114  md5Update(&md5Context[1], password, passwordLen);
115  //Add the salt string to digest B
116  md5Update(&md5Context[1], salt, saltLen);
117  //Add the password again to digest B
118  md5Update(&md5Context[1], password, passwordLen);
119  //Finish digest B
120  md5Final(&md5Context[1], digest);
121 
122  //For each block of 64 bytes in the password string, add digest B to digest
123  //A. For the remaining N bytes of the password string add the first N bytes
124  //of digest B to digest A
125  for(j = 0; j < passwordLen; j += n)
126  {
127  n = MIN(passwordLen - j, MD5_DIGEST_SIZE);
128  md5Update(&md5Context[0], digest, n);
129  }
130 
131  //Process each bit of the binary representation of the length of the password
132  //string up to and including the highest 1-digit, starting from to lowest bit
133  //position
134  for(n = passwordLen; n > 0; n >>= 1)
135  {
136  //Check the value of the current bit
137  if((n & 1) != 0)
138  {
139  //For a 1-digit add 0 to digest A
140  md5Update(&md5Context[0], "", 1);
141  }
142  else
143  {
144  //For a 0-digit add the first character of the key
145  md5Update(&md5Context[0], password, 1);
146  }
147  }
148 
149  //Finish digest A
150  md5Final(&md5Context[0], digest);
151 
152  //Apply 1000 rounds of calculation
153  for(i = 0; i < MD5_CRYPT_ROUNDS; i++)
154  {
155  //Start digest C
156  md5Init(&md5Context[0]);
157 
158  //Odd or even round?
159  if((i & 1) != 0)
160  {
161  //For odd round numbers add the password
162  md5Update(&md5Context[0], password, passwordLen);
163  }
164  else
165  {
166  //For even round numbers add the last digest
167  md5Update(&md5Context[0], digest, MD5_DIGEST_SIZE);
168  }
169 
170  //Round number not divisible by 3?
171  if(i % 3 != 0)
172  {
173  //For all round numbers not divisible by 3 add the salt
174  md5Update(&md5Context[0], salt, saltLen);
175  }
176 
177  //Round number not divisible by 7?
178  if(i % 7 != 0)
179  {
180  //For all round numbers not divisible by 7 add the password
181  md5Update(&md5Context[0], password, passwordLen);
182  }
183 
184  //Odd or even round?
185  if((i & 1) != 0)
186  {
187  //For odd round numbers add the last digest
188  md5Update(&md5Context[0], digest, MD5_DIGEST_SIZE);
189  }
190  else
191  {
192  //For even round numbers add the password
193  md5Update(&md5Context[0], password, passwordLen);
194  }
195 
196  //Finish intermediate digest
197  md5Final(&md5Context[0], digest);
198  }
199 
200  //The output string is an ASCII string that begins with the salt prefix
201  osStrcpy(output, "$1$");
202  n = 3;
203 
204  //The salt string truncated to 16 characters
205  saltLen = MIN(saltLen, MD5_CRYPT_MAX_SALT_LEN);
206 
207  //Append the salt string
208  osStrncpy(output + n, salt, saltLen);
209  n += saltLen;
210 
211  //Append a '$' character
212  output[n++] = '$';
213 
214  //Append the base-64 encoded final C digest
215  n += md5CryptEncodeBase64(digest, output + n);
216 
217 #if (CRYPTO_STATIC_MEM_SUPPORT == DISABLED)
218  //Release hash context
219  cryptoFreeMem(md5Context);
220 #endif
221 
222  //Length of the output string (excluding the terminating NULL)
223  if(outputLen != NULL)
224  {
225  *outputLen = n;
226  }
227 
228  //Successful processing
229  return NO_ERROR;
230 }
231 
232 
233 /**
234  * @brief base-64 encoding algorithm
235  * @param[in] input MD5 digest to encode
236  * @param[out] output NULL-terminated string encoded with base-64 algorithm
237  * @return Length of the base-64 string
238  **/
239 
240 size_t md5CryptEncodeBase64(const uint8_t *input, uint8_t *output)
241 {
242  uint32_t value;
243  uint_t i;
244  uint_t j;
245 
246  //Encode the MD5 digest using base-64
247  for(i = 0, j = 0; i < 5; i++)
248  {
249  //Extract a group of three bytes from the digest
250  value = input[i] << 16;
251  value |= input[i + 6] << 8;
252  value |= (i < 4) ? input[i + 12] : input[5];
253 
254  //Each group produces four characters as output
255  output[j++] = base64EncTable[value & 0x3F];
256  output[j++] = base64EncTable[(value >> 6) & 0x3F];
257  output[j++] = base64EncTable[(value >> 12) & 0x3F];
258  output[j++] = base64EncTable[(value >> 18) & 0x3F];
259  }
260 
261  //For the last group there are not enough bytes left in the digest and
262  //the value zero is used in its place
263  value = input[11];
264 
265  //The last group produces two characters as output
266  output[j++] = base64EncTable[value & 0x3F];
267  output[j++] = base64EncTable[(value >> 6) & 0x3F];
268 
269  //Properly terminate the string with a NULL character
270  output[j] = '\0';
271 
272  //Return the length of the base-64 string
273  return j;
274 }
275 
276 #endif
277 
size_t md5CryptEncodeBase64(const uint8_t *input, uint8_t *output)
base-64 encoding algorithm
Definition: md5_crypt.c:240
void md5Final(Md5Context *context, uint8_t *digest)
Finish the MD5 message digest.
@ ERROR_OUT_OF_MEMORY
Definition: error.h:63
#define MD5_CRYPT_MAX_SALT_LEN
Definition: md5_crypt.h:41
#define osStrlen(s)
Definition: os_port.h:168
void md5Init(Md5Context *context)
Initialize MD5 message digest context.
#define MD5_CRYPT_ROUNDS
Definition: md5_crypt.h:38
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
error_t
Error codes.
Definition: error.h:43
General definitions for cryptographic algorithms.
MD5 algorithm context.
Definition: md5.h:62
#define MIN(a, b)
Definition: os_port.h:63
#define MD5_DIGEST_SIZE
Definition: md5.h:45
Unix crypt using MD5.
char char_t
Definition: compiler_port.h:55
uint8_t n
MD5 (Message-Digest Algorithm)
#define cryptoFreeMem(p)
Definition: crypto.h:826
#define osStrncpy(s1, s2, length)
Definition: os_port.h:216
uint8_t value[]
Definition: tcp.h:376
#define cryptoAllocMem(size)
Definition: crypto.h:821
#define osStrncmp(s1, s2, length)
Definition: os_port.h:180
unsigned int uint_t
Definition: compiler_port.h:57
#define osStrcpy(s1, s2)
Definition: os_port.h:210
void md5Update(Md5Context *context, const void *data, size_t length)
Update the MD5 context with a portion of the message being hashed.
@ NO_ERROR
Success.
Definition: error.h:44
error_t md5Crypt(const char_t *password, const char_t *salt, char_t *output, size_t *outputLen)
MD5-crypt algorithm.
Definition: md5_crypt.c:61