1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
// Author: cirakg & claude ai
// Summary: Byte-array helpers: hex and UTF-8 conversion, concatenation, wiping and constant-time comparison.
//
// Security notes:
// * wipe() overwrites a buffer in place. Call it on every buffer that held a
// key or a decrypted secret as soon as it is no longer needed. Monkey C
// can't guarantee no other copy exists (README 7.8), so this is best effort.
// * equal() always looks at every byte, so comparing a MAC doesn't reveal
// through its running time how many leading bytes matched.
import Toybox.Lang;
import Toybox.StringUtil;
module Bytes {
// Hex text (any case, even length) to bytes. Empty text gives an empty array.
function fromHex(hex as String) as ByteArray {
if (hex.length() == 0) {
return []b;
}
return StringUtil.convertEncodedString(hex, {
:fromRepresentation => StringUtil.REPRESENTATION_STRING_HEX,
:toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY
}) as ByteArray;
}
// Bytes to lowercase hex text.
function toHex(data as ByteArray) as String {
if (data.size() == 0) {
return "";
}
return (StringUtil.convertEncodedString(data, {
:fromRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
:toRepresentation => StringUtil.REPRESENTATION_STRING_HEX
}) as String).toLower();
}
// The UTF-8 bytes of a string.
function fromUtf8(text as String) as ByteArray {
if (text.length() == 0) {
return []b;
}
return StringUtil.convertEncodedString(text, {
:fromRepresentation => StringUtil.REPRESENTATION_STRING_PLAIN_TEXT,
:toRepresentation => StringUtil.REPRESENTATION_BYTE_ARRAY,
:encoding => StringUtil.CHAR_ENCODING_UTF8
}) as ByteArray;
}
// A new array holding a followed by b; neither input is changed.
function concat(a as ByteArray, b as ByteArray) as ByteArray {
var out = new [0]b;
out.addAll(a);
out.addAll(b);
return out;
}
// A new array with a copy of data.
function copy(data as ByteArray) as ByteArray {
var out = new [0]b;
out.addAll(data);
return out;
}
// Overwrite every byte with zero. Accepts null so callers can wipe fields
// that may not have been set.
function wipe(data as ByteArray?) as Void {
if (data == null) {
return;
}
for (var i = 0; i < data.size(); i++) {
data[i] = 0;
}
}
// True when both arrays have the same length and content. The running time
// depends only on the length, not on where the first difference is.
function equal(a as ByteArray, b as ByteArray) as Boolean {
if (a.size() != b.size()) {
return false;
}
var diff = 0;
for (var i = 0; i < a.size(); i++) {
diff = diff | (a[i] ^ b[i]);
}
return diff == 0;
}
// XOR source into target, byte by byte (same length required).
function xorInto(target as ByteArray, source as ByteArray) as Void {
for (var i = 0; i < target.size(); i++) {
target[i] = target[i] ^ source[i];
}
}
// An unsigned 64-bit value as 8 bytes, most significant first (the HOTP
// counter encoding of RFC 4226).
function uint64BigEndian(value as Long) as ByteArray {
var out = new [8]b;
var v = value;
for (var i = 7; i >= 0; i--) {
out[i] = (v & 0xFFl).toNumber();
v = v >> 8;
}
return out;
}
// An unsigned 32-bit value as 4 bytes, most significant first.
function uint32BigEndian(value as Number) as ByteArray {
var out = new [4]b;
out.encodeNumber(value, Lang.NUMBER_FORMAT_UINT32, {:offset => 0, :endianness => Lang.ENDIAN_BIG});
return out;
}
}
|