@@ -8,37 +8,124 @@ import java.security.DigestInputStream
88import java.security.MessageDigest
99import java.util.zip.CRC32
1010import java.util.zip.CheckedInputStream
11+ import javax.crypto.Mac
12+ import javax.crypto.spec.SecretKeySpec
1113
1214class IntegrityCalculationService {
1315
1416 companion object {
1517
16- fun calculateIntegrity (data : ByteArray , algorithm : String? ): IntegrityCalculationResult {
17- if (algorithm.isNullOrEmpty()) return IntegrityCalculationResult (" " , " " )
18- val integriteitAlgoritme = IntegriteitAlgoritme .valueOf(algorithm)
19- return when (integriteitAlgoritme) {
20- IntegriteitAlgoritme .CRC_32 -> {
21- val crc32 = CRC32 ()
22- crc32.update(data)
23- IntegrityCalculationResult (crc32.value.toString(16 ), algorithm)
24- }
18+ // ── CRC-16/IBM (poly 0xA001, reflected) ─────────────────────────────
19+ private val CRC16_TABLE : IntArray = IntArray (256 ) { i ->
20+ var crc = i
21+ repeat(8 ) { crc = if (crc and 1 != 0 ) (crc ushr 1 ) xor 0xA001 else crc ushr 1 }
22+ crc
23+ }
2524
26- IntegriteitAlgoritme .MD5 ,
27- IntegriteitAlgoritme .SHA_1 ,
28- IntegriteitAlgoritme .SHA_256 ,
29- -> {
30- val javaAlgo = when (integriteitAlgoritme) {
31- IntegriteitAlgoritme .MD5 -> " MD5"
32- IntegriteitAlgoritme .SHA_1 -> " SHA-1"
33- else -> " SHA-256"
34- }
35- val hash = MessageDigest .getInstance(javaAlgo).digest(data)
36- .joinToString(" " ) { " %02x" .format(it) }
37- IntegrityCalculationResult (hash, algorithm)
38- }
25+ private fun crc16 (data : ByteArray ): Int {
26+ var crc = 0
27+ for (b in data) crc = (crc ushr 8 ) xor CRC16_TABLE [(crc xor b.toInt()) and 0xFF ]
28+ return crc
29+ }
30+
31+ // ── CRC-64/ECMA-182 (poly 0xC96C5795D7870F42) ───────────────────────
32+ private val CRC64_TABLE : LongArray = LongArray (256 ) { i ->
33+ var crc = i.toLong()
34+ repeat(8 ) { crc = if (crc and 1L != 0L ) (crc ushr 1 ) xor - 0x3693a86a2777a96bL else crc ushr 1 }
35+ crc
36+ }
37+
38+ private fun crc64 (data : ByteArray ): Long {
39+ var crc = - 1L
40+ for (b in data) crc = (crc ushr 8 ) xor CRC64_TABLE [((crc xor b.toLong()) and 0xFF ).toInt()]
41+ return crc.inv ()
42+ }
3943
40- else -> IntegrityCalculationResult (" " , " " )
44+ // ── Fletcher checksums ───────────────────────────────────────────────
45+ private fun fletcher4 (data : ByteArray ): Int {
46+ var s1 = 0
47+ var s2 = 0
48+ for (b in data) {
49+ s1 = (s1 + (b.toInt() and 0x03 )) % 15
50+ s2 = (s2 + s1) % 15
4151 }
52+ return (s2 shl 4 ) or s1
53+ }
54+
55+ private fun fletcher8 (data : ByteArray ): Int {
56+ var s1 = 0
57+ var s2 = 0
58+ for (b in data) {
59+ s1 = (s1 + (b.toInt() and 0xFF )) % 255
60+ s2 = (s2 + s1) % 255
61+ }
62+ return (s2 shl 8 ) or s1
63+ }
64+
65+ private fun fletcher16 (data : ByteArray ): Int {
66+ var s1 = 0
67+ var s2 = 0
68+ for (b in data) {
69+ s1 = (s1 + (b.toInt() and 0xFF )) % 65535
70+ s2 = (s2 + s1) % 65535
71+ }
72+ return (s2 shl 16 ) or s1
73+ }
74+
75+ private fun fletcher32 (data : ByteArray ): Long {
76+ var s1 = 0L
77+ var s2 = 0L
78+ for (b in data) {
79+ s1 = (s1 + (b.toLong() and 0xFF )) % 4294967295L
80+ s2 = (s2 + s1) % 4294967295L
81+ }
82+ return (s2 shl 32 ) or s1
83+ }
84+
85+ // ── HMAC-SHA256 with empty key (integrity without shared secret) ─────
86+ private fun hmacSha256 (data : ByteArray ): String {
87+ val mac = Mac .getInstance(" HmacSHA256" )
88+ mac.init (SecretKeySpec (ByteArray (0 ), " HmacSHA256" ))
89+ return mac.doFinal(data).joinToString(" " ) { " %02x" .format(it) }
90+ }
91+
92+ // ── Shared helpers ───────────────────────────────────────────────────
93+ private fun hashBytesWithMessageDigest (data : ByteArray , algo : IntegriteitAlgoritme ): String {
94+ val javaAlgo = algo.toJavaMessageDigestName()
95+ val hash = MessageDigest .getInstance(javaAlgo).digest(data)
96+ return hash.joinToString(" " ) { " %02x" .format(it) }
97+ }
98+
99+ private fun IntegriteitAlgoritme.toJavaMessageDigestName (): String = when (this ) {
100+ IntegriteitAlgoritme .MD5 -> " MD5"
101+ IntegriteitAlgoritme .SHA_1 -> " SHA-1"
102+ IntegriteitAlgoritme .SHA_256 -> " SHA-256"
103+ else -> throw IllegalArgumentException (" Not a MessageDigest algorithm: $this " )
104+ }
105+
106+ private fun computeHash (data : ByteArray , algo : IntegriteitAlgoritme ): String = when (algo) {
107+ IntegriteitAlgoritme .CRC_16 -> crc16(data).toString(16 )
108+ IntegriteitAlgoritme .CRC_32 -> {
109+ val crc32 = CRC32 ()
110+ crc32.update(data)
111+ crc32.value.toString(16 )
112+ }
113+ IntegriteitAlgoritme .CRC_64 -> crc64(data).toULong().toString(16 )
114+ IntegriteitAlgoritme .FLETCHER_4 -> fletcher4(data).toString(16 )
115+ IntegriteitAlgoritme .FLETCHER_8 -> fletcher8(data).toString(16 )
116+ IntegriteitAlgoritme .FLETCHER_16 -> fletcher16(data).toString(16 )
117+ IntegriteitAlgoritme .FLETCHER_32 -> fletcher32(data).toString(16 )
118+ IntegriteitAlgoritme .HMAC -> hmacSha256(data)
119+ IntegriteitAlgoritme .MD5 ,
120+ IntegriteitAlgoritme .SHA_1 ,
121+ IntegriteitAlgoritme .SHA_256 ,
122+ -> hashBytesWithMessageDigest(data, algo)
123+ }
124+
125+ fun calculateIntegrity (data : ByteArray , algorithm : String? ): IntegrityCalculationResult {
126+ if (algorithm.isNullOrEmpty()) return IntegrityCalculationResult (" " , " " )
127+ val algo = IntegriteitAlgoritme .valueOf(algorithm)
128+ return IntegrityCalculationResult (computeHash(data, algo), algorithm)
42129 }
43130
44131 /* *
@@ -50,13 +137,18 @@ class IntegrityCalculationService {
50137 *
51138 * When [algorithm] is null or blank [block] is called with the original [stream] and
52139 * empty strings are returned, so the upload still proceeds normally.
140+ *
141+ * Note: CRC_16, CRC_64, Fletcher variants, and HMAC do not have standard Java streaming
142+ * filter support. For those algorithms the stream bytes are accumulated and hashed after
143+ * [block] completes. All MessageDigest-based algorithms (MD5, SHA_1, SHA_256) and CRC_32
144+ * use true streaming filters.
53145 */
54146 fun <T > withIntegrity (stream : InputStream , algorithm : String? , block : (InputStream ) -> T ): Pair <T , IntegrityCalculationResult > {
55147 if (algorithm.isNullOrEmpty()) {
56148 return block(stream) to IntegrityCalculationResult (" " , " " )
57149 }
58- val integriteitAlgoritme = IntegriteitAlgoritme .valueOf(algorithm)
59- return when (integriteitAlgoritme ) {
150+ val algo = IntegriteitAlgoritme .valueOf(algorithm)
151+ return when (algo ) {
60152 IntegriteitAlgoritme .CRC_32 -> {
61153 val crc32 = CRC32 ()
62154 val cis = CheckedInputStream (stream, crc32)
@@ -68,19 +160,26 @@ class IntegrityCalculationService {
68160 IntegriteitAlgoritme .SHA_1 ,
69161 IntegriteitAlgoritme .SHA_256 ,
70162 -> {
71- val javaAlgo = when (integriteitAlgoritme) {
72- IntegriteitAlgoritme .MD5 -> " MD5"
73- IntegriteitAlgoritme .SHA_1 -> " SHA-1"
74- else -> " SHA-256"
75- }
76- val digest = MessageDigest .getInstance(javaAlgo)
163+ val digest = MessageDigest .getInstance(algo.toJavaMessageDigestName())
77164 val digestStream = DigestInputStream (stream, digest)
78165 val result = block(digestStream)
79166 val hash = digest.digest().joinToString(" " ) { " %02x" .format(it) }
80167 result to IntegrityCalculationResult (hash, algorithm)
81168 }
82169
83- else -> block(stream) to IntegrityCalculationResult (" " , " " )
170+ // No standard streaming filter — buffer and hash after block completes.
171+ IntegriteitAlgoritme .CRC_16 ,
172+ IntegriteitAlgoritme .CRC_64 ,
173+ IntegriteitAlgoritme .FLETCHER_4 ,
174+ IntegriteitAlgoritme .FLETCHER_8 ,
175+ IntegriteitAlgoritme .FLETCHER_16 ,
176+ IntegriteitAlgoritme .FLETCHER_32 ,
177+ IntegriteitAlgoritme .HMAC ,
178+ -> {
179+ val buffer = stream.readAllBytes()
180+ val result = block(buffer.inputStream())
181+ result to IntegrityCalculationResult (computeHash(buffer, algo), algorithm)
182+ }
84183 }
85184 }
86185 }
0 commit comments