Skip to content

Commit bd6265a

Browse files
committed
Adds CryptDataBuilder
- **Breaking Changes**: Uses string for salt and hash in `CryptData` - New class `CryptDataBuilder` is available to construct `CryptData` instances.
1 parent 8e3d37a commit bd6265a

File tree

8 files changed

+363
-233
lines changed

8 files changed

+363
-233
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 2.4.0
2+
3+
- **Breaking Changes**: Uses string for salt and hash in `CryptData`
4+
- New class `CryptDataBuilder` is available to construct `CryptData` instances.
5+
16
# 2.3.0
27

38
- **Breaking Changes**: Renames PHCSF -> CryptFormat. Affected names:

lib/src/codecs/crypt.dart

Lines changed: 0 additions & 229 deletions
This file was deleted.

lib/src/codecs/crypt/crypt.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) 2024, Sudipto Chandra
2+
// All rights reserved. Check LICENSE file for details.
3+
4+
export 'crypt_codec.dart';
5+
export 'crypt_data.dart';
6+
export 'crypt_data_builder.dart';
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) 2024, Sudipto Chandra
2+
// All rights reserved. Check LICENSE file for details.
3+
4+
import 'dart:convert';
5+
6+
import 'crypt_data.dart';
7+
8+
/// The encoder used by the [CryptFormat] codec
9+
class CryptEncoder extends Converter<CryptData, String> {
10+
const CryptEncoder();
11+
12+
@override
13+
String convert(CryptData input) {
14+
input.validate();
15+
String result = '\$${input.id}';
16+
if (input.version != null && input.version!.isNotEmpty) {
17+
result += '\$v=${input.version!}';
18+
}
19+
if (input.params != null && input.params!.isNotEmpty) {
20+
result += '\$';
21+
result += input.params!.entries
22+
.map((entry) => '${entry.key}=${entry.value}')
23+
.join(',');
24+
}
25+
if (input.salt != null && input.salt!.isNotEmpty) {
26+
result += '\$';
27+
result += input.salt!;
28+
}
29+
if (input.hash != null && input.hash!.isNotEmpty) {
30+
result += '\$';
31+
result += input.hash!;
32+
}
33+
return result;
34+
}
35+
}
36+
37+
/// The decoder used by the [CryptFormat] codec
38+
class CryptDecoder extends Converter<String, CryptData> {
39+
const CryptDecoder();
40+
41+
@override
42+
CryptData convert(String input) {
43+
String id, name, value;
44+
String? version, salt, hash;
45+
Map<String, String>? params;
46+
47+
Iterable<String> parts = input.split('\$');
48+
49+
if (parts.isEmpty) {
50+
throw FormatException('Empty string');
51+
}
52+
parts = parts.skip(1);
53+
54+
if (parts.isEmpty) {
55+
throw FormatException('Invalid PHC string format');
56+
}
57+
id = parts.first;
58+
59+
parts = parts.skip(1);
60+
61+
if (parts.isNotEmpty && parts.first.startsWith('v=')) {
62+
version = parts.first.substring(2);
63+
parts = parts.skip(1);
64+
}
65+
66+
if (parts.isNotEmpty && parts.first.contains('=')) {
67+
params = {};
68+
for (final kv in parts.first.split(',')) {
69+
var pair = kv.split('=');
70+
if (pair.length != 2) {
71+
throw FormatException('Invalid param format: $kv');
72+
}
73+
name = pair[0];
74+
value = pair[1];
75+
params[name] = value;
76+
}
77+
parts = parts.skip(1);
78+
}
79+
80+
if (parts.isNotEmpty) {
81+
salt = parts.first;
82+
parts = parts.skip(1);
83+
}
84+
85+
if (parts.isNotEmpty) {
86+
hash = parts.first;
87+
parts = parts.skip(1);
88+
}
89+
90+
if (parts.isNotEmpty) {
91+
throw FormatException('Invalid PHC string format');
92+
}
93+
94+
var data = CryptData(
95+
id,
96+
version: version,
97+
salt: salt,
98+
hash: hash,
99+
params: params,
100+
);
101+
102+
try {
103+
data.validate();
104+
} on ArgumentError catch (e) {
105+
throw FormatException(e.message, e);
106+
}
107+
108+
return data;
109+
}
110+
}
111+
112+
/// Provides encoding and decoding of [PHC string format][phc] data.
113+
///
114+
/// **PHC string format** is a standardized way to represent password hashes
115+
/// generated by the competing password hashing algorithms. This format is
116+
/// designed to ensure consistency and interoperability between different
117+
/// password hashing implementations.
118+
///
119+
/// The string format specifiction:
120+
/// ```
121+
/// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
122+
/// ```
123+
///
124+
/// [phc]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
125+
class CryptFormat extends Codec<CryptData, String> {
126+
const CryptFormat();
127+
128+
@override
129+
final encoder = const CryptEncoder();
130+
131+
@override
132+
final decoder = const CryptDecoder();
133+
}

0 commit comments

Comments
 (0)