HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Truncate 160-bit output from SHA-1 to 64-bit uint64_t

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
bit160shaoutputuint64_ttruncatefrom

Problem

I'd like to truncate 160-bit output from SHA-1 to receive a (weaker) 64-bit digest.

It has been a while since I did the type of low-level C pointer arithmetic in the but-last line. Could you please review it for me? It should return the last 64 bits of the the byte array in a uint64_t. (I guess conceptually I could just as well take the first 64 bits, which would make the code simpler but here we are.)

#import 

- (uint64_t)digestFromString: (NSString *)s {
    NSData *input = [s dataUsingEncoding: NSUTF8StringEncoding];
    unsigned char output[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(input.bytes, (CC_LONG)input.length, output);
    uint64_t digest;
    NSAssert(CC_SHA1_DIGEST_LENGTH >= sizeof(uint64_t), nil);
    digest = *(uint64_t *)(output + CC_SHA1_DIGEST_LENGTH - sizeof(uint64_t));
    return digest;
}


How could this code be improved upon?

UPDATE Here is a second (alternative) version with an attempt at avoiding any alignment issues. (Its much more straigtforward to truncate to the first 64 bits in this case.) Actual digests will differ from the 1st version.

- (uint64_t)digestFromString: (NSString *)s {
    NSData *input = [s dataUsingEncoding: NSUTF8StringEncoding];
    union { // uint64_t-aligned
        uint64_t      uint64s[CC_SHA1_DIGEST_LENGTH / sizeof(uint64_t) + sizeof(uint64_t)];
        unsigned char bytes[CC_SHA1_DIGEST_LENGTH];
    } output;
    CC_SHA1(input.bytes, (CC_LONG)input.length, (unsigned char *)output.bytes);
    return output.uint64s[0];
}

Solution

Both your methods should work. As far as I know, there is no alignment problem
on the current OS X and iOS architectures in your first method (see below).

The code from the second method can be simplified a tiny bit:

- (uint64_t)digestFromString: (NSString *)s {
    NSData *input = [s dataUsingEncoding: NSUTF8StringEncoding];
    union { // uint64_t-aligned
        uint64_t      digest;
        unsigned char bytes[CC_SHA1_DIGEST_LENGTH];
    } output;
    CC_SHA1(input.bytes, (CC_LONG)input.length, output.bytes);
    return output.digest;
}


The first element in the union need not be an array, and casting output.bytes
to unsigned char * is not necessary (the array automatically becomes a pointer
to the first element when passed as a function argument).

But the easiest method to extract a 64-bit integer from the digest would be to use the inline function

OS_INLINE uint64_t _OSReadInt64(
    const volatile void               * base,
    uintptr_t                     byteOffset
)


from OSByteOrder.h that comes with the OS X and iOS SDKs:

uint64_t digest = _OSReadInt64(output, CC_SHA1_DIGEST_LENGTH - sizeof(uint64_t)); // last 8 bytes
uint64_t digest = _OSReadInt64(output, 0); // first 8 bytes


This function is implemented as

return *(volatile uint64_t *)((uintptr_t)base + byteOffset);


which indicates that there is no alignment problem when casting an
arbitrary pointer to uint64_t *. But that may be different on future
architectures, with using the above function you are on the safe side.

Alternatively, you can use memcpy:

uint64_t digest;
memcpy(&digest, output, sizeof digest);


Instead of

NSAssert(CC_SHA1_DIGEST_LENGTH >= sizeof(uint64_t), nil);


you could use a "compile-time/static assertion":

_Static_assert(CC_SHA1_DIGEST_LENGTH >= sizeof(uint64_t), "");


_Static_assert is a GCC extension which is also understood by the Clang
compiler, so you can use it in your Xcode projects.

The advantage is that it causes a compiler error if the condition is
not satisfied, instead of failing later at runtime.

Code Snippets

- (uint64_t)digestFromString: (NSString *)s {
    NSData *input = [s dataUsingEncoding: NSUTF8StringEncoding];
    union { // uint64_t-aligned
        uint64_t      digest;
        unsigned char bytes[CC_SHA1_DIGEST_LENGTH];
    } output;
    CC_SHA1(input.bytes, (CC_LONG)input.length, output.bytes);
    return output.digest;
}
OS_INLINE uint64_t _OSReadInt64(
    const volatile void               * base,
    uintptr_t                     byteOffset
)
uint64_t digest = _OSReadInt64(output, CC_SHA1_DIGEST_LENGTH - sizeof(uint64_t)); // last 8 bytes
uint64_t digest = _OSReadInt64(output, 0); // first 8 bytes
return *(volatile uint64_t *)((uintptr_t)base + byteOffset);
uint64_t digest;
memcpy(&digest, output, sizeof digest);

Context

StackExchange Code Review Q#79489, answer score: 6

Revisions (0)

No revisions yet.