XmlBinaryNodeWriter序列化gMSA Unicode密码用于Web服务传输失败
背景
我们使用Microsoft HPC Pack 2019 SP3 SDK提交HPC作业,HPC原生不支持Active Directory gMSA账户,因此通过AD获取gMSA账户密码。该MSA密码为256字节,以Unicode(UTF16?-.NET显示密码长度为128)表示。
错误现象
当尝试通过HPC Web服务提交包含该密码的作业时,XmlBinaryNodeWriter序列化失败,抛出以下错误:
Exception thrown: 'System.Text.EncoderFallbackException' in mscorlib.dll
Unable to translate Unicode character \uDC33 at index 43 to specified code page.
我们猜测可能是不可显示字符导致序列化失败,已提供部分脱敏的gMSA密码截图。
补充信息
查看调用栈后发现,MSA密码被尝试以UTF8序列化,但实际为UTF16,需要了解如何告知XmlObjectSerializer该字符串为UTF16而非UTF8。
序列化失败的具体原因
错误中的\uDC33是低代理项(Low Surrogate)字符,这类字符无法单独存在,必须与对应的高代理项(High Surrogate)配对才能构成有效的UTF-16代码点。当XmlBinaryNodeWriter尝试将其按UTF-8序列化时,孤立的低代理项不属于合法Unicode字符,编码器无法完成转换,因此抛出EncoderFallbackException。
本质问题是从AD获取的256字节密码数据被错误转换为字符串:直接将UTF-16字节数组当作字符串处理时,可能出现未配对的代理项(比如字节数组长度异常、数据截断/损坏),导致生成的字符串包含无效字符,最终在UTF-8编码环节失败。
解决思路:正确处理gMSA密码的序列化
XmlObjectSerializer默认采用UTF-8编码,且字符串在.NET中本身就是UTF-16字符序列,无法直接指定“字符串是UTF-16”。核心解决方案是避免将密码字节数组当作字符串处理,而是以二进制安全的方式传递:
将密码字节数组转换为Base64字符串(推荐)
把原始256字节的密码数据编码为Base64字符串,所有二进制数据会被转换为可安全序列化的ASCII字符,彻底规避无效Unicode代理项问题。示例代码:byte[] gmsaPasswordBytes = // 从AD获取的256字节密码数据 string passwordBase64 = Convert.ToBase64String(gmsaPasswordBytes);提交作业时传递该Base64字符串,接收端通过
Convert.FromBase64String即可还原为原始字节数组。清理字符串中的无效代理项(不推荐)
如果必须传递字符串形式的密码,可在序列化前清理无效代理项,但此操作可能修改原始密码数据导致验证失败:string invalidPassword = // 包含无效代理项的密码字符串 string cleanedPassword = string.Concat( invalidPassword.Normalize(NormalizationForm.FormD) .Where(c => char.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark) );
内容的提问来源于stack exchange,提问作者Jon Barker

