使用 DES 算法对数据加密

DES算法

提供高质量的数据保护,防止数据未经授权的泄露和未被察觉的修改

具有相当高的复杂性,使得破译的开销超过可能获得的利益,同时又要便于理解和掌握

DES密码体制的安全性应该不依赖于算法的保密,其安全性仅以加密密钥的保密为基础

实现经济,运行有效,并且适用于多种完全不同的应用

 

苹果本身支持DES加密,在项目中引入头文件 CommonCrypto/CommonCryptor.h 即可使用相关函数.

我自己对其进行了封装,支持ARC与非ARC

YXCrypto.h

//
//  YXCrypto.h
//  用秘钥给字符串加密或者解密
//
//  Created by YouXian on 14-3-18.
//  Copyright (c) 2014年 YouXian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface YXCrypto : NSObject

/*!
 * 给字符串加密
 */
+ (NSString *)DesEncryptString:(NSString*)src WithKey:(NSString *)key;

/*!
 * 给字符串解密
 */
+ (NSString *)DesDecryptString:(NSString*)src WithKey:(NSString *)key;

@end

YXCrypto.m

//
//  YXCrypto.m
//  用秘钥给字符串加密或者解密
//
//  Created by YouXian on 14-3-18.
//  Copyright (c) 2014年 YouXian. All rights reserved.
//

#import "YXCrypto.h"
#import <CommonCrypto/CommonCryptor.h>

#if __has_feature(objc_arc)
// ARC
#define Auto_Release(obj)
#define Safe_Release(obj)
#else
// 非ARC
#define Auto_Release(obj) [obj autorelease]
#define Safe_Release(obj) [obj release]; obj = nil
#endif

static YXCrypto *shareInstance = nil;

@implementation YXCrypto

/*!
 * 给字符串加密
 */
+ (NSString *)DesEncryptString:(NSString*)src WithKey:(NSString *)key {
    NSString* strRet = @"";

    if (shareInstance == nil)
    {
        shareInstance = [[YXCrypto alloc] init];
    }

    // encrypt source content
    NSData* bytes = [src dataUsingEncoding:NSUTF8StringEncoding];
    NSData* data = [shareInstance DesCryptWithOperation:kCCEncrypt
                                                  bytes:bytes
                                                    key:key];

    // format bytes to visible string
    char* pBuff = (char*)[data bytes];
    for (int i=0; i<data.length; i++) {
        strRet = [strRet stringByAppendingFormat:@"%02X", pBuff[i]& 0xFF];
    }
    return strRet;
}

/*!
 * 给字符串解密
 */
+ (NSString *)DesDecryptString:(NSString*)src WithKey:(NSString *)key
{
    if (shareInstance == nil)
    {
        shareInstance = [[YXCrypto alloc] init];
    }

    static unsigned char _map_ch2hex[] =
    {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
        0, 0, 0, 0, 0, 0, 0,    // :, ;, <, =, >, ?, @,
        0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    };

    // decode source content to bytes
    unsigned char* bytes = (unsigned char*)malloc((src.length+1)*sizeof(unsigned char));
    [[src uppercaseString] getCString:(char*)bytes maxLength:src.length+1 encoding:NSUTF8StringEncoding];
    unsigned char *p1 = bytes, *p2 = bytes;
    int n = src.length/2;
    for (int i=0; i<n; i++) {
        *p1 = _map_ch2hex[*p2-'0'] * 0x10 + _map_ch2hex[*(p2+1)-'0'];
        p1++;
        p2+=2;
    }
    NSData* data = [NSData dataWithBytes:bytes
                                  length:n];

    // decrypt source bytes
    NSData* dataOut = [shareInstance DesCryptWithOperation:kCCDecrypt
                                                     bytes:data
                                                       key:key];
    free(bytes);

    NSString* strRet = [[NSString alloc] initWithData:dataOut
                                             encoding:NSUTF8StringEncoding];
    Auto_Release(strRet);

    return strRet;
}

- (NSData *)DesCryptWithOperation:(CCOperation)operation bytes:(NSData*)bytes key:(NSString *)key {

    NSUInteger dataLength = [bytes length];

    size_t bufferSize = ([bytes length] + kCCBlockSizeDES) & ~(kCCBlockSizeDES - 1);
    unsigned char *buffer = (unsigned char *)malloc(bufferSize*sizeof(unsigned char));
    memset((void*)buffer, 0, bufferSize);

    size_t numBytesCrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(operation,
                                          kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding | kCCOptionECBMode,
                                          (void const*)[key UTF8String],
                                          kCCKeySizeDES,
                                          NULL,
                                          [bytes bytes], dataLength,
                                          (void*)buffer, bufferSize,
                                          &numBytesCrypted);
    NSData* dataRet = nil;
    if (cryptStatus == kCCSuccess) {
        dataRet = [[NSData alloc] initWithBytes:buffer length:numBytesCrypted];
        Auto_Release(dataRet);
    }
    free(buffer);
    return dataRet;
}

@end

使用:

 

 

附录1:https://github.com/alfaromeodev/Cryptor

//
//  Cryptor.h
//  test
//
//  Created by Da Zhang on 11/5/12.
//  Copyright 2012 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCryptor.h>

@interface Cryptor : NSObject {

}

/*
 all the methods below only support utf8 string
 */
+ (NSString *)encodeMD5:(NSString *)str;

+ (NSString *)encodeDES:(NSString *)plainString key:(NSString *)key;
+ (NSString *)decodeDES:(NSString *)decodedString key:(NSString*)key;

+ (NSString *)encodeBase64:(NSString *)plainString;
+ (NSString *)decodeBase64:(NSString *)decodedString;

@end
//
//  Cryptor.m
//  test
//
//  Created by Da Zhang on 11/5/12.
//  Copyright 2012 __MyCompanyName__. All rights reserved.
//

#import "Cryptor.h"

@interface Cryptor ()

+ (NSString *)encodeBase64WithData:(NSData *)objData;
+ (NSData *)decodeBase64WithUTF8String:(NSString *)strBase64;
+ (NSString *)parseByte2HexString:(Byte *)bytes;

@end

static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const short _base64DecodingTable[256] = {
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
    -2,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
    -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};

@implementation Cryptor

#pragma mark private section
+ (NSData *)decodeBase64WithUTF8String:(NSString *)strBase64 {
    const char * objPointer = [strBase64 cStringUsingEncoding:NSUTF8StringEncoding];
    int intLength = strlen(objPointer);
    int intCurrent;
    int i = 0, j = 0, k;

    unsigned char * objResult;
    objResult = calloc(intLength, sizeof(char));

    // Run through the whole string, converting as we go
    while ( ((intCurrent = *objPointer++) != '\0') && (intLength-- > 0) ) {
        if (intCurrent == '=') {
            if (*objPointer != '=' && ((i % 4) == 1)) {// || (intLength > 0)) {
                // the padding character is invalid at this point -- so this entire string is invalid
                free(objResult);
                return nil;
            }
            continue;
        }

        intCurrent = _base64DecodingTable[intCurrent];
        if (intCurrent == -1) {
            // we're at a whitespace -- simply skip over
            continue;
        } else if (intCurrent == -2) {
            // we're at an invalid character
            free(objResult);
            return nil;
        }

        switch (i % 4) {
            case 0:
                objResult[j] = intCurrent << 2;
                break;

            case 1:
                objResult[j++] |= intCurrent >> 4;
                objResult[j] = (intCurrent & 0x0f) << 4;
                break;

            case 2:
                objResult[j++] |= intCurrent >>2;
                objResult[j] = (intCurrent & 0x03) << 6;
                break;

            case 3:
                objResult[j++] |= intCurrent;
                break;
        }
        i++;
    }

    // mop things up if we ended on a boundary
    k = j;
    if (intCurrent == '=') {
        switch (i % 4) {
            case 1:
                // Invalid state
                free(objResult);
                return nil;

            case 2:
                k++;
                // flow through
            case 3:
                objResult[k] = 0;
        }
    }

    // Cleanup and setup the return NSData
    NSData * objData = [[[NSData alloc] initWithBytes:objResult length:j] autorelease];
    free(objResult);
    return objData;
}

+ (NSString *)encodeBase64WithData:(NSData *)objData {
    const unsigned char * objRawData = [objData bytes];
    char * objPointer;
    char * strResult;

    // Get the Raw Data length and ensure we actually have data
    int intLength = [objData length];
    if (intLength == 0) return nil;

    // Setup the String-based Result placeholder and pointer within that placeholder
    strResult = (char *)calloc(((intLength + 2) / 3) * 4, sizeof(char));
    objPointer = strResult;

    // Iterate through everything
    while (intLength > 2) { // keep going until we have less than 24 bits
        *objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
        *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
        *objPointer++ = _base64EncodingTable[((objRawData[1] & 0x0f) << 2) + (objRawData[2] >> 6)];
        *objPointer++ = _base64EncodingTable[objRawData[2] & 0x3f];

        // we just handled 3 octets (24 bits) of data
        objRawData += 3;
        intLength -= 3;
    }

    // now deal with the tail end of things
    if (intLength != 0) {
        *objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
        if (intLength > 1) {
            *objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
            *objPointer++ = _base64EncodingTable[(objRawData[1] & 0x0f) << 2];
            *objPointer++ = '=';
        } else {
            *objPointer++ = _base64EncodingTable[(objRawData[0] & 0x03) << 4];
            *objPointer++ = '=';
            *objPointer++ = '=';
        }
    }

    // Terminate the string-based result
    *objPointer = '\0';

    // Return the results as an NSString object
    return [NSString stringWithCString:strResult encoding:NSUTF8StringEncoding];
}

+ (NSString *)parseByte2HexString:(Byte *)bytes {
    NSMutableString *hexStr = [[NSMutableString alloc]init];
    int i = 0;
    if(bytes) {
        while (bytes[i] != '\0') {
            NSString *hexByte = [NSString stringWithFormat:@"%x",bytes[i] & 0xff];///16进制数
            if([hexByte length]==1) [hexStr appendFormat:@"0%@", hexByte];
            else [hexStr appendFormat:@"%@", hexByte];
            i++;
        }
    }
    //NSLog(@"bytes 的16进制数为:%@",hexStr);
    return hexStr;
}

#pragma mark public section
+ (NSString *)encodeMD5:(NSString *)str {

    const char *cStr = [str UTF8String];

    unsigned char result[CC_MD5_DIGEST_LENGTH];

    CC_MD5( cStr, strlen(cStr), result );

    return [NSString 

            stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",

            result[0], result[1],

            result[2], result[3],

            result[4], result[5],

            result[6], result[7],

            result[8], result[9],

            result[10], result[11],

            result[12], result[13],

            result[14], result[15]

            ];

}

+ (NSString *)encodeBase64:(NSString *)plainString {
    return [Cryptor encodeBase64WithData:[plainString dataUsingEncoding:NSUTF8StringEncoding]];
}

+ (NSString *)decodeBase64:(NSString *)decodedString {
    NSData * objData = [Cryptor decodeBase64WithUTF8String:decodedString];
    return [[NSString alloc] initWithData:objData encoding:NSUTF8StringEncoding];
}

+ (NSString *)encodeDES:(NSString *)plainString key:(NSString *)key {
    NSString *ciphertext = nil;
    const char *textBytes = [plainString UTF8String];
    NSUInteger dataLength = strlen(textBytes);
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    Byte iv[] = {1,2,3,4,5,6,7,8};
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String],
                                          kCCKeySizeDES,
                                          iv,
                                          textBytes,
                                          dataLength,
                                          buffer,
                                          1024,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        ciphertext = [[Cryptor encodeBase64WithData:data] autorelease];
    }
    return ciphertext;
}

+ (NSString *)decodeDES:(NSString*)decodedString key:(NSString*)key {
    NSData* cipherData = [Cryptor decodeBase64WithUTF8String:decodedString];
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    size_t numBytesDecrypted = 0;
    Byte iv[] = {1,2,3,4,5,6,7,8};
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String],
                                          kCCKeySizeDES,
                                          iv,
                                          [cipherData bytes],
                                          [cipherData length],
                                          buffer,
                                          1024,
                                          &numBytesDecrypted);
    NSString* plainText = nil;
    if (cryptStatus == kCCSuccess) {
        NSData* data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
        plainText = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    }
    return plainText;
}

@end

附录2:AES加密

NSData+AES.h + NSData+AES.m

#import <Foundation/Foundation.h>

/**
 * Adds AES encryption and decryption capabilities to NSData.
 */
@interface NSData (AES)

/**
 * Encrypt NSData using AES256 with a given symmetric encryption key.
 * @param key The symmetric encryption key
 */
- (NSData *)AES256EncryptWithKey:(NSString *)key;

/**
 * Decrypt NSData using AES256 with a given symmetric encryption key.
 * @param key The symmetric encryption key
 */
- (NSData *)AES256DecryptWithKey:(NSString *)key;

@end
#import "NSData+AES.h"
#import <CommonCrypto/CommonCryptor.h>

@implementation NSData (AES)

- (NSData *)AES256EncryptWithKey:(NSString *)key
{
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

- (NSData *)AES256DecryptWithKey:(NSString *)key {
    // 'key' should be 32 bytes for AES256, will be null-padded otherwise
    char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
    bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)

    // fetch key data
    [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];

    NSUInteger dataLength = [self length];

    //See the doc: For block ciphers, the output size will always be less than or
    //equal to the input size plus the size of one block.
    //That's why we need to add the size of one block here
    size_t bufferSize = dataLength + kCCBlockSizeAES128;
    void *buffer = malloc(bufferSize);

    size_t numBytesDecrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                          keyPtr, kCCKeySizeAES256,
                                          NULL /* initialization vector (optional) */,
                                          [self bytes], dataLength, /* input */
                                          buffer, bufferSize, /* output */
                                          &numBytesDecrypted);

    if (cryptStatus == kCCSuccess) {
        //the returned NSData takes ownership of the buffer and will free it on deallocation
        return [NSData dataWithBytesNoCopy:buffer length:numBytesDecrypted];
    }

    free(buffer); //free the buffer;
    return nil;
}

@end

时间: 2024-09-20 16:47:23

使用 DES 算法对数据加密的相关文章

通过PHP的内置函数,通过DES算法对数据加密和解密_php技巧

由于项目的需要,要写一个能生成"授权码"的类(授权码主要包含项目使用的到期时间),生成的授权码将会写入到一个文件当中,每当项目运行的时候,会自动读取出文件中的密文,然后使用唯一的"密钥"来调用某个函数,对密文进行解密,从中解读出项目的使用到期时间. 之前,自己有先试着写了下,主要是base64+md5+反转字符串.算法太过简单,很容易被破解,而且也没有能过做到"密钥"在加解密中的重要性,故而舍之. 后来,查找了相关资料,发现,原来PHP中内置了一

DES算法及其在VC++6.0下的实现(上)

摘要: 本文介绍了一种国际上通用的加密算法-DES算法的原理,并给出了在VC++6.0语言环境下实现的源代码.最后给出一个示例,以供参考. 关键字:DES算法.明文.密文.密钥.VC: 本文程序运行效果图如下: 正文: 当今社会是信息化的社会.为了适应社会对计算机数据安全保密越来越高的要求,美国国家标准局(NBS)于1997年公布了一个由IBM公司研制的一种加密算法,并且确定为非机要部门使用的数据加密标准,简称DES(Data Encrypton Standard).自公布之日起,DES算法作为

DES算法解析

DES算法  美国国家标准局1973年开始研究除国防部外的其它部门的计算机系统的数据加密标准,于1973年5月15日和1974年8月27日先后两次向公众发出了征求加密算法的公告. 1977年1月,美国政府颁布:采纳IBM公司设计的方案作为非机密数据的正式数据加密标准(DES,Data Encryption Standard). 一.DES算法  美国国家标准局1973年开始研究除国防部外的其它部门的计算机系统的数据加密标准,于1973年5月15日和1974年8月27日先后两次向公众发出了征求加密

加密算法之DES算法

一.DES算法美国国家标准局1973年开始研究除国防部外的其它部门的计算机系统的数据加密标准,于1973年5月15日和1974年8月27日先后两次向公众发出了征求加密算法的公告.加密算法要达到的目的(通常称为DES 密码算法要求)主要为以下四点: ☆提供高质量的数据保护, 防止数据未经授权的泄露和未被察觉的修改: ☆具有相当高的 复杂性,使得破译的开销超过可能获得的利益,同时又要便于理解和掌握: ☆DES密码体制的安全性应该不依赖于算法的保密,其安全性仅以加密密钥的保密为基础: ☆实现经济,运行

Swift 使用RSA算法进行数据加密,解密以及数字签名

RSA算法是一种非对称加密算法,要了解RSA算法,首先要知道什么是对称加密算法,什么是非对称加密算法. 1,对称加密算法 密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密. 特点:算法公开.计算量小.加密速度快.加密效率高特点.但交易双方都使用同样钥匙,安全性得不到保证. 具体算法有:DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法. 2,非对称加密算法 非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey).

DES算法加密解密代码解析

问题描述 下面是一段DES算法,对于DES我不是很懂,麻烦帮我讲解一下下面的加密解密怎么实现,各行的意思.谢谢!!publicDES(){InitializeComponent();}//密钥//获取或设置对称算法的机密密钥.机密密钥既用于加密,也用于解密.为了保证对称算法的安全,必须只有发送方和接收方知道该机密密钥.有效密钥大小是由特定对称算法实现指定的,密钥大小在LegalKeySizes中列出.privatestaticbyte[]DESKey=newbyte[]{11,23,93,102

如何实现 DES 算法

算法 How to implement the Data Encryption Standard (DES) A step by step tutorial Version 1.2 The Data Encryption Standard (DES) algorithm, adopted by the U.S. government in 1977, is a block cipher that transforms 64-bit data blocks under a 56-bit secre

轻松实现DES算法查看器

DES(Data Encrypton Standard) 算法的实现网上已经有很多,本人在此讲述 的是在DES算法加密过程中如何查看16迭代过程中生成的Ki,Li,Ri,Fi,Si等,这 样可以当做一个DES加密对照器,这样可以方便的发现你在加密过程中出现的错 误! 图一 :程序运行界面 本程序用了一个列表框来显示所有16次迭代的所有信息,并 在选择一栏后,在下面的编辑框中显示详细信息,这样就可以不必在列表框中拖 曳鼠标,这样方便拷贝! 程序介绍: 采取的编程语言是微软的VC6.0,大小为184

DES算法及其在VC++6.0下的实现(下)

在<DES算法及其在VC++6.0下的实现(上)>中主要介绍了DES算法的基本原理,下面让我们继续: 二.子密钥的生成 64比特的密钥生成16个48比特的子密钥.其生成过程见图: 子密钥生成过程具体解释如下: 64比特的密钥K,经过PC-1后,生成56比特的串.其下标如表所示: PC-1 57 49 41 33 25 17 9 1 58 50 42 34 26 18 10 2 59 51 43 35 27 19 11 3 60 52 44 36 63 55 47 39 31 23 15 7 6