+
+static void dbuf(const char *m, const Byte *a, int l) {
+ fprintf(stderr,"dbuf %s l=%d ",m,l);
+ while (l-->0) fprintf(stderr,"%02x",*a++);
+ putc('\n',stderr);
+}
+
+int do_hbytes_hmac(ClientData cd, Tcl_Interp *ip, const HashAlgInfo *alg,
+ HBytes_Value message, Tcl_Obj *key_obj,
+ Tcl_Obj *maclen_obj, HBytes_Value *result) {
+ /* key->alpha = state after H(K XOR ipad <unfinished>
+ * key->beta = state after H(K XOR opad <unfinished>
+ * key->buffers = room for one block, or one state
+ */
+ CiphKeyValue *key;
+ Byte *dest;
+ int i, ml, rc;
+
+ if (maclen_obj) {
+ rc= Tcl_GetIntFromObj(ip, maclen_obj, &ml); if (rc) return rc;
+ if (ml<0 || ml>alg->hashsize)
+ return staticerr(ip, "requested hmac output size out of range");
+ } else {
+ ml= alg->hashsize;
+ }
+
+ key= get_key(ip, key_obj, alg,
+ alg->blocksize > alg->statesize
+ ? alg->blocksize : alg->statesize);
+
+ if (!key->alpha) {
+ assert(!key->beta);
+
+ if (key->valuelen > alg->blocksize)
+ return staticerr(ip, "key to hmac longer than hash block size");
+
+dbuf("start key",key->value,key->valuelen);
+ memcpy(key->buffers, key->value, key->valuelen);
+ memset(key->buffers + key->valuelen, 0, alg->blocksize - key->valuelen);
+ for (i=0; i<alg->blocksize; i++) key->buffers[i] ^= 0x36;
+
+ key->alpha= TALLOC(alg->statesize);
+ alg->init(key->alpha);
+dbuf("inner key",key->buffers,alg->blocksize);
+ alg->update(key->alpha, key->buffers, alg->blocksize);
+
+ key->beta= TALLOC(alg->statesize);
+ alg->init(key->beta);
+ for (i=0; i<alg->blocksize; i++) key->buffers[i] ^= (0x5c ^ 0x36);
+ alg->update(key->beta, key->buffers, alg->blocksize);
+dbuf("inner key",key->buffers,alg->blocksize);
+ }
+ assert(key->beta);
+
+ dest= hbytes_arrayspace(result, alg->hashsize);
+
+ memcpy(key->buffers, key->alpha, alg->statesize);
+ alg->update(key->buffers, hbytes_data(&message), hbytes_len(&message));
+ alg->final(key->buffers, dest);
+dbuf("inner hash",dest,alg->hashsize);
+
+ memcpy(key->buffers, key->beta, alg->statesize);
+ alg->update(key->buffers, dest, alg->hashsize);
+ alg->final(key->buffers, dest);
+dbuf("outer hash",dest,alg->hashsize);
+
+ hbytes_unappend(result, alg->hashsize - ml);
+
+ return TCL_OK;
+}