在PowerShell中生成Ghost博客JWT令牌遇签名无效问题,及C#十六进制转字节数组的优化方案咨询
问题核心分析
你遇到的「Invalid token: invalid signature」错误,根源完全在密钥处理逻辑上。Ghost提供的$id:$secret格式里,secret是一个十六进制字符串,而你的PowerShell代码错误地把它当成ASCII字符串来处理,这和Ruby的[secret].pack('H*')(十六进制转字节数组)完全不是一回事。
先明确Ghost要求的JWT签名逻辑:
Ghost的JWT签名需要将十六进制格式的secret转换为原始字节数组,再用这个字节数组作为HMAC-SHA256的密钥来签名JWT内容。
PowerShell实现修正与优化
首先,你需要替换错误的密钥处理代码,正确将十六进制secret转为字节数组。另外,New-Jwt模块的-Secret参数如果接受字节数组的话,直接传入转换后的字节数组即可,不需要再做Base64相关操作。
修正后的完整PowerShell代码
# 拆分Ghost提供的id:secret令牌 $parts = $adminToken -split ":" $id = $parts[0] $hexSecret = $parts[1] # 关键:将十六进制字符串转为字节数组(替代你之前错误的Base64处理) function Convert-HexToByteArray { param([string]$Hex) if ($Hex.Length % 2 -ne 0) { throw "Hex string must have an even length." } $bytes = New-Object byte[] ($Hex.Length / 2) for ($i = 0; $i -lt $Hex.Length; $i += 2) { $bytes[$i/2] = [Convert]::ToByte($Hex.Substring($i, 2), 16) } return $bytes } $secretBytes = Convert-HexToByteArray -Hex $hexSecret # 安装并导入JWT模块(如果未安装) if (-not (Get-Module -ListAvailable -Name JWT)) { Install-Module -Name JWT -Force -Scope CurrentUser } Import-Module JWT # 构建JWT Header和Payload(用-Compress减少冗余) $header = @{ alg = "HS256" kid = $id typ = "JWT" } | ConvertTo-Json -Compress $payload = @{ exp = ([DateTimeOffset](Get-Date).AddMinutes(5)).ToUnixTimeSeconds() iat = ([DateTimeOffset](Get-Date)).ToUnixTimeSeconds() aud = "/admin/" } | ConvertTo-Json -Compress # 生成JWT令牌(注意这里传入字节数组作为Secret) $jwtToken = New-Jwt -Header $header -PayloadJson $payload -Secret $secretBytes # 调用Ghost Admin API,记得添加Content-Type头 $headers = @{ "Authorization" = "Ghost $jwtToken" "Content-Type" = "application/json" } Invoke-RestMethod -Uri "https://scrumbug.ghost.io/ghost/api/admin/pages/$($trainingPage.id)" -Method PUT -Body ($trainingPage | ConvertTo-Json -Compress) -Headers $headers
为什么之前的代码错了?
你的原代码$secret= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($secret)) | ConvertFrom-Base64UrlString | ConvertTo-Base64UrlString做了完全无关的操作:把十六进制字符串当成ASCII字符转成字节,再转Base64,这和Ghost要求的「十六进制直接转原始字节」逻辑完全不符,导致签名密钥错误,自然会触发INVALID_JWT。
C#实现优化
你自己写的ByteConvertor类是可行的,但在**.NET 5及以上版本**中,已经有内置的Convert.FromHexString方法可以直接完成十六进制字符串到字节数组的转换,不需要自己实现循环:
优化后的C#代码(使用官方JWT库)
首先,安装System.IdentityModel.Tokens.Jwt NuGet包,然后实现JWT生成:
using System; using System.IdentityModel.Tokens.Jwt; using Microsoft.IdentityModel.Tokens; public class GhostJwtGenerator { public static string GenerateGhostAdminToken(string adminToken) { // 拆分id和secret var parts = adminToken.Split(':'); var clientId = parts[0]; var hexSecret = parts[1]; // .NET 5+ 直接用内置方法转十六进制到字节数组 var secretBytes = Convert.FromHexString(hexSecret); // 构建JWT签名凭据 var securityKey = new SymmetricSecurityKey(secretBytes); var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); // 配置JWT的Header和Payload var tokenDescriptor = new SecurityTokenDescriptor { IssuedAt = DateTime.UtcNow, Expires = DateTime.UtcNow.AddMinutes(5), Audience = "/admin/", SigningCredentials = signingCredentials, Header = new JwtHeader { {"alg", SecurityAlgorithms.HmacSha256}, {"kid", clientId}, {"typ", "JWT"} } }; // 生成并返回JWT令牌 var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(jwtToken); } }
兼容旧.NET版本的实现
如果你用的是.NET Framework或.NET Core 3.1及以下,没有Convert.FromHexString,可以简化你原有的ByteConvertor类,增加参数校验让它更健壮:
public static class ByteConvertor { public static byte[] StringToByteArray(string hex) { if (string.IsNullOrWhiteSpace(hex)) throw new ArgumentNullException(nameof(hex), "Hex string cannot be null or empty."); if (hex.Length % 2 != 0) throw new ArgumentException("Hex string must have an even length.", nameof(hex)); byte[] bytes = new byte[hex.Length / 2]; for (int i = 0; i < hex.Length; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; } }
验证要点
不管用PowerShell还是C#,生成JWT后可以用在线JWT验证工具检查:
- 粘贴生成的JWT令牌
- 在验证签名区域,使用原始的十六进制secret(注意选择「非Base64编码密钥」选项)
- 确认Header的
alg为HS256、kid为你的client id,Payload的aud为/admin/,且签名验证通过
内容的提问来源于stack exchange,提问作者jessehouwing

