NSData 不接受有效的 base64 编码字符串
Posted
技术标签:
【中文标题】NSData 不接受有效的 base64 编码字符串【英文标题】:NSData won't accept valid base64 encoded string 【发布时间】:2014-02-19 19:17:46 【问题描述】:我正在 ios (7) cient 端实现 JSON Web Token 身份验证。它运作良好。我的应用接收令牌,并可以使用它们对我的服务器进行经过身份验证的调用。
现在,我希望我的客户端代码检查令牌的到期日期,以便知道何时重新进行身份验证。检查 JWT 身份验证令牌的到期日期很简单。授权令牌是 3 个 base64 编码的 JSON blob,用“.”分隔。 - 过期时间戳位于中间 blob 中,位于名为 ext
的字段中。自 unix 时代以来已经过了几秒钟。
所以我的代码看起来像这样:
- (NSDate*) expirationDate
if ( !_tokenAppearsValid ) return nil;
if ( !_parsedExpirationDate )
//
// Token is three base64 encoded payloads separated by '.'
// The payload we want is the middle one, which is a JSON dict, with
// 'exp' being the unix seconds timestamp of the expiration date
// Returning nil is appropriate if no 'exp' is findable
//
NSArray *components = [self.token componentsSeparatedByString:@"."];
NSString *payload = components[1];
NSData* payloadJsonData = [[NSData alloc]
initWithBase64EncodedString:payload
options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSError* jsonError = nil;
NSDictionary* payloadJson = [NSJSONSerialization JSONObjectWithData:payloadJsonData options:0 error:&jsonError];
if ( payloadJson )
if ( payloadJson[@"exp"] )
NSTimeInterval timestampSeconds = [payloadJson[@"exp"] doubleValue];
_expirationDate = [NSDate dateWithTimeIntervalSince1970:timestampSeconds];
_parsedExpirationDate = YES;
return _expirationDate;
问题很简单。中间的base64 blob,当被NSData -initWithBase64EncodedString 解析为nil
- 这很糟糕。
我检查了 base64 blob,它似乎是有效的。我的服务器目前正在返回虚拟数据,所以这里有一个示例 blob:
eyJlbWFpbCI6ImZvb0BiYXIuYmF6IiwiYWNjb3VudElkIjoiMTIzNDUtNjc4OTAtYmFyLWJheiIsImV4cCI6MTM5MDkxNTAzNywiaWF0IjoxMzkwOTE0MTM3fQ
解码为:
"email":"foo@bar.baz","accountId":"12345-67890-bar-baz","exp":1390915037,"iat":1390914137
我在这里测试过:http://www.base64decode.org
我在我的应用程序的其他地方成功地使用了 NSData 的 base64 方法 - 我认为我在这里没有做任何特别糟糕的事情。但我全是耳朵!有什么想法吗?
【问题讨论】:
您的 base64 数据似乎被截断了。所以解码后的字符串不是有效的 json 数据。 【参考方案1】:Paul's answer 的 Swift 版本
func paddedBase64EncodedString(encodedString: String) -> String
let encodedStringLength = encodedString.characters.count
let paddedLength = encodedStringLength + (4 - (encodedStringLength % 4))
let paddedBase64String = encodedString.stringByPaddingToLength(paddedLength,
withString: "=",
startingAtIndex: 0)
return paddedBase64String
【讨论】:
这个答案在某些情况下会创建一个“====”的填充。第二行末尾缺少“% 4”。【参考方案2】:我遇到了同样的问题,但通过在字符串末尾添加 ==
解决了它
base64UserStr = NSString(format: "%@%@", base64UserStr,"==") as String
let decodedData = NSData(base64EncodedString: base64UserStr, options: NSDataBase64DecodingOptions.init(rawValue: 0))
if (decodedData != nil)
let decodedString = NSString(data: decodedData!, encoding: NSUTF8StringEncoding)
print("Base 64 decode string is \(decodedString)")
这肯定行得通。
【讨论】:
【参考方案3】:虽然马丁的回答是正确的,但这里有一个快速且正确(!)的方法来解决这个问题:
NSString *base64String = @"<the token>";
NSUInteger paddedLength = base64String.length + (4 - (base64String.length % 4));
NSString* correctBase64String = [base64String stringByPaddingToLength:paddedLength withString:@"=" startingAtIndex:0];
【讨论】:
这个答案在某些情况下会创建一个“====”的填充。第二行末尾缺少“% 4”。 克拉斯是正确的。 paddedLength 计算不正确。这是第 2 行的正确代码(由 Klass 建议) NSUInteger paddedLength = base64NotPaddedString.length + ((4 - (base64NotPaddedString.length % 4)) % 4);【参考方案4】:这是一个适当填充 Base-64 字符串并适用于 iOS 4+ 的解决方案:
NSData+Base64.h
@interface NSData (Base64)
/**
Returns a data object initialized with the given Base-64 encoded string.
@param base64String A Base-64 encoded NSString
@returns A data object built by Base-64 decoding the provided string. Returns nil if the data object could not be decoded.
*/
- (instancetype) initWithBase64EncodedString:(NSString *)base64String;
/**
Create a Base-64 encoded NSString from the receiver's contents
@returns A Base-64 encoded NSString
*/
- (NSString *) base64EncodedString;
@end
NSData+Base64.m
@interface NSString (Base64)
- (NSString *) stringPaddedForBase64;
@end
@implementation NSString (Base64)
- (NSString *) stringPaddedForBase64
NSUInteger paddedLength = self.length + (self.length % 3);
return [self stringByPaddingToLength:paddedLength withString:@"=" startingAtIndex:0];
@end
@implementation NSData (Base64)
- (instancetype) initWithBase64EncodedString:(NSString *)base64String
return [self initWithBase64Encoding:[base64String stringPaddedForBase64]];
- (NSString *) base64EncodedString
return [self base64Encoding];
@end
【讨论】:
自 iOS 7.0 以来您似乎有一些弃用。 另外,你确定它有效吗?不应该是 % 4 吗? %3 根据 Base64 规范是正确的。至于弃用,它们是哪些?【参考方案5】:您的 Base64 字符串无效。它必须用=
字符填充
长度是 4 的倍数。在您的情况下:"eyJlbWFp....MTM3fQ=="
。
使用此填充,initWithBase64EncodedString
可以正确解码 Base64 字符串。
【讨论】:
Base64 数据的填充不是强制性的,请参阅 RFC 的第 3.2 节:faqs.org/rfcs/rfc4648.html @droussel:感谢您的反馈。 NSData 类中的方法不接受 Base64 字符串,该字符串未填充到 4 的倍数。我无法判断这是 Apple 框架中的错误还是有意的。 是的,你说得对,initWithBase64EncodedString 不接受未填充的 base64。这不是错误,但他们可以提供该功能作为选项。同时,您始终可以像这样自己填充字符串: int padLenght = (4 - (base64.length % 4)) % 4; NSString *paddedBase64 = [NSString stringWithFormat:@"%s%.*s", [base64 UTF8String], padLenght, "=="]; @droussel 你错了。该标准规定“实现必须在编码数据的末尾包含适当的填充字符,除非引用本文档的规范另有明确说明。base64 和 base32 字母使用填充,如下文第 4 节和第 6 节所述”。另请参阅第 4 节。 Base64 编码唯一可以跳过填充的情况是数据的长度已经明确知道(或单独传递)。此处不是这种情况,因此剥离填充是无效的。 @russbishop JWT 需要未填充的 base64。它在rfc-editor.org/rfc/rfc7515.txt 中指定,并且根据 rfc 4648 的第 3.2 节允许:在某些情况下,不需要或不使用在基本编码数据中使用填充 ("=")。在一般情况下,当无法对传输数据的大小做出假设时,需要填充以产生正确的解码数据。以上是关于NSData 不接受有效的 base64 编码字符串的主要内容,如果未能解决你的问题,请参考以下文章