如何使用keytab解密Kerberos票据获取用户名及相关参数说明
Kerberos keytab解密Ticket提取用户名实现指南
核心概念说明
- ccache全称为Credentials Cache(凭证缓存),是MIT Kerberos库用来存储客户端侧已获取Kerberos凭证的本地存储结构。你当前仅需实现服务端票据解密、提取用户名的场景完全不需要用到ccache,你之前查阅的两份官方示例属于客户端工具/协议扩展测试场景,需要读取客户端自身持有的凭证,才会涉及ccache相关逻辑,和你当前的服务端验票场景无关,这部分代码可以直接忽略。
核心函数参数说明
你提到的krb5_server_decrypt_ticket_keytab()是MIT Kerberos官方提供的服务端票据解密入口,各参数含义如下:
krb5_context context:Kerberos上下文句柄,所有Kerberos API调用都需要先初始化该上下文,通过krb5_init_context()即可获取krb5_keytab keytab:已加载的keytab文件句柄,通过krb5_kt_resolve()传入keytab文件路径加载得到krb5_ticket *ticket:待解密的票据结构体,你持有的base64编码票据需要先做base64解码得到二进制流,再通过krb5_decode_ticket()解析为该结构体- 返回值:返回0代表解密校验成功,非0为对应错误码,可通过
krb5_get_error_message()获取可读的错误描述
该函数在解密的同时会自动完成票据全链路合法性校验,包括时间有效期、签名有效性、内容是否篡改,返回0即代表票据合法,无需额外增加校验逻辑。
完整实现流程
你仅需持有的base64编码票据、keytab文件两个输入即可完成全流程,步骤如下:
- 初始化Kerberos上下文,调用
krb5_init_context()获取上下文句柄 - 加载keytab文件,调用
krb5_kt_resolve()传入keytab绝对路径拿到keytab句柄 - 对base64编码的票据做解码,得到二进制格式的票据原始字节流
- 调用
krb5_decode_ticket()将二进制字节流解析为krb5_ticket类型的结构体指针对象 - 调用
krb5_server_decrypt_ticket_keytab()传入上下文、keytab句柄、票据指针完成解密校验 - 校验通过后,用户名存储在
ticket->enc_part2->client->data字段,对应的用户所属域存储在ticket->enc_part2->client->realm.data字段 - 按顺序释放票据、keytab、上下文等资源,避免内存泄漏
最小可运行代码示例
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <krb5.h> #include <openssl/bio.h> #include <openssl/evp.h> // 简易base64解码实现,可替换为任意base64解码库 int base64_decode(const char *b64input, unsigned char **out, int *out_len) { BIO *b64 = BIO_new(BIO_f_base64()); BIO *bmem = BIO_new_mem_buf(b64input, strlen(b64input)); b64 = BIO_push(b64, bmem); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); *out_len = BIO_read(b64, *out, strlen(b64input)); BIO_free_all(b64); return 0; } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s <keytab_abs_path> <base64_encoded_ticket>\n", argv[0]); return 1; } const char *keytab_path = argv[1]; const char *b64_ticket = argv[2]; krb5_context ctx = NULL; krb5_keytab kt = NULL; krb5_ticket *ticket = NULL; krb5_error_code ret; unsigned char *ticket_bin = NULL; int ticket_bin_len = 0; // 初始化Kerberos上下文 ret = krb5_init_context(&ctx); if (ret) { fprintf(stderr, "Init krb context failed, code: %d\n", ret); goto cleanup; } // 加载keytab文件 ret = krb5_kt_resolve(ctx, keytab_path, &kt); if (ret) { fprintf(stderr, "Load keytab failed: %s\n", krb5_get_error_message(ctx, ret)); goto cleanup; } // 解码base64票据 ticket_bin = malloc(strlen(b64_ticket)); memset(ticket_bin, 0, strlen(b64_ticket)); base64_decode(b64_ticket, &ticket_bin, &ticket_bin_len); // 解析二进制票据为结构体 krb5_data ticket_data = make_data(ticket_bin, ticket_bin_len); ret = krb5_decode_ticket(&ticket_data, &ticket); if (ret) { fprintf(stderr, "Parse ticket failed: %s\n", krb5_get_error_message(ctx, ret)); goto cleanup; } // 用keytab解密并校验票据 ret = krb5_server_decrypt_ticket_keytab(ctx, kt, ticket); if (ret) { fprintf(stderr, "Decrypt ticket failed: %s\n", krb5_get_error_message(ctx, ret)); goto cleanup; } // 提取用户名和域信息 char *username = ticket->enc_part2->client->data; char *realm = ticket->enc_part2->client->realm.data; printf("Ticket valid. Username: %s@%s\n", username, realm); cleanup: if (ticket_bin) free(ticket_bin); if (ticket) krb5_free_ticket(ctx, ticket); if (kt) krb5_kt_close(ctx, kt); if (ctx) krb5_free_context(ctx); return ret; }
编译时链接Kerberos和OpenSSL库即可,编译命令:gcc -o ticket_decrypt ticket_decrypt.c -lkrb5 -lcrypto
官方示例说明
你之前找到的两份官方示例均不适用于当前场景:
- kvno.c是客户端侧的密钥版本号查询工具,涉及ccache的逻辑是为了读取客户端自身的TGT凭证,用于向KDC请求服务端密钥版本,和服务端验票场景无关
- s4u2proxy.c是Kerberos S4U2代理扩展的测试代码,涉及ccache的逻辑是为了存储代理服务自身的凭证,用于实现代用户请求其他服务票据的逻辑,同样不属于纯服务端验票场景
两份示例的运行依赖完整的Kerberos客户端环境、KDC连通性,不需要为了当前需求调试运行这两份代码,直接使用上述最小实现即可。
内容的提问来源于stack exchange,提问作者Rachel91
相关产品推荐
相关产品推荐

