Yes, this is possible for any CRC.
Here it is for your CRC, in C (this can be easily translated to most any other programming language — just be careful with integer sizes, as well as shifting if converting to Java):
#include <stdint.h>
// Return the product of the polynomials a and b, modulo the CRC polynomial.
// a cannot be zero.
static uint64_t multmodp(uint64_t a, uint64_t b) {
uint64_t prod = 0;
for (;;) {
if (a & 0x8000000000000000) {
prod ^= b;
if ((a & 0x7fffffffffffffff) == 0)
break;
}
a <<= 1;
b = b & 1 ? (b >> 1) ^ 0x9a6c9329ac4bc9b5 : b >> 1;
}
return prod;
}
// table_comb[n] is the polynomial x^2^n, modulo the CRC polynomial.
static uint64_t const table_comb[] = {
0x4000000000000000, 0x2000000000000000, 0x0800000000000000, 0x0080000000000000,
0x0000800000000000, 0x0000000080000000, 0x9a6c9329ac4bc9b5, 0x10f4bb0f129310d6,
0x70f05dcea2ebd226, 0x311211205672822d, 0x2fc297db0f46c96e, 0xca4d536fabf7da84,
0xfb4cdc3b379ee6ed, 0xea261148df25140a, 0x59ccb2c07aa6c9b4, 0x20b3674a839af27a,
0x2d8e1986da94d583, 0x42cdf4c20337635d, 0x1d78724bf0f26839, 0xb96c84e0afb34bd5,
0x5d2e1fcd2df0a3ea, 0xcd9506572332be42, 0x23bda2427f7d690f, 0x347a953232374f07,
0x1c2a807ac2a8ceea, 0x9b92ad0e14fe1460, 0x2574114889f670b2, 0x4a84a6c45e3bf520,
0x915bbac21cd1c7ff, 0xb0290ec579f291f5, 0xcf2548505c624e6e, 0xb154f27bf08a8207,
0xce4e92344baf7d35, 0x51da8d7e057c5eb3, 0x9fb10823f5be15df, 0x73b825b3ff1f71cf,
0x5db436c5406ebb74, 0xfa7ed8f3ec3f2bca, 0xc4d58efdc61b9ef6, 0xa7e39e61e855bd45,
0x97ad46f9dd1bf2f1, 0x1a0abb01f853ee6b, 0x3f0827c3348f8215, 0x4eb68c4506134607,
0x4a46f6de5df34e0a, 0x2d855d6a1c57a8dd, 0x8688da58e1115812, 0x5232f417fc7c7300,
0xa4080fb2e767d8da, 0xd515a7e17693e562, 0x1181f7c862e94226, 0x9e23cd058204ca91,
0x9b8992c57a0aed82, 0xb2c0afb84609b6ff, 0x2f7160553a5ea018, 0x3cd378b5c99f2722,
0x814054ad61a3b058, 0xbf766189fce806d8, 0x85a5e898ac49f86f, 0x34830d11bc84f346,
0x9644d95b173c8c1c, 0x150401ac9ac759b1, 0xebe1f7f46fb00eba, 0x8ee4ce0c2e2bd662
};
// Return the polynomial x^(8n) modulo the CRC polynomial.
static uint64_t x8nmodp(uintmax_t n) {
uint64_t xp = 0x8000000000000000;
int k = 3;
for (;;) {
if (n & 1)
xp = multmodp(table_comb[k], xp);
n >>= 1;
if (n == 0)
break;
if (++k == 64)
k = 0;
}
return xp;
}
// Return the CRC-64/NVME of the concatenation of two blocks of bytes, where the
// first block's CRC is crc1, the second block's CRC is crc2, and its length in
// bytes is len2.
uint64_t crc64nvme_comb(uint64_t crc1, uint64_t crc2, uintmax_t len2) {
return multmodp(x8nmodp(len2), crc1) ^ crc2;
}
You do not need the length of the block for the first CRC, just the second, in order to compute the CRC of the first block concatenated with the second. So, crc64nvme_comb(crc64nvme("abc"), crc64nvme("def"), 3) == crc64nvme("abcdef").
That code was generated by crcany. It uses the parameters from Greg Cook's list of CRCs, which contains for CRC-64/NVME:
width=64 poly=0xad93d23594c93659 init=0xffffffffffffffff refin=true refout=true xorout=0xffffffffffffffff check=0xae8b14860a799888 residue=0xf310303b2b6f6e42 name="CRC-64/NVME"
This is already very fast, taking O(log(len2)) time. However if you're doing this repeatedly for the same value of len2, e.g. breaking the data into blocks that are all of the same length (except the last), then you can pre-compute x8nmodp(len2) and re-use that result for each block. Then each use would be O(1) time.