Source Code

And You And I is no open or shared source code project. However, as it is a security app, the relevant parts are open for review.

AYAI 3.0 Decrypter Reference Code

The following program can be used to verify how And You And I 3.0 encrypts its documents. It is a simple command line tool that takes your AYAI password, the number of AES128 key generator iterations (the app supports 10 as old v2.0 standard and the much better 100,000), an AYAI file e.g. taken from your local iCloud Drive folder, and optionally the output path for the plain file.

Just compile it on your Mac, don´t forget to add libcrypto and libssl.

To my knowledge, there is no Visual Studio or GCC code that allows to implement the Foundation Class objects used for serializing the data. You may drop this part and just use the native C functions to decrypt the file with OpenSSL.


//
//  main.c
//  AYAI30Decryptor
//
//  Copyright (c) 2013-2015 Stephan André. All rights reserved.
//

#import <Foundation/Foundation.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#import "openssl/evp.h"
#import "openssl/err.h"
#import "openssl/aes.h"

@interface AYAIAttachment : NSObject <NSCoding>
@property (strong, nonatomic) NSString *comment;
@property (strong, nonatomic) NSString *filename;
@property (strong, nonatomic) NSString *realfilename;
@property (strong, nonatomic) NSString *realfiletype;
@property (strong, nonatomic) NSData *data;
- (id)initWithCoder:(NSCoder *)decoder;
@end

@implementation AYAIAttachment
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
    self.comment = [decoder decodeObjectForKey:@"comment"];
    self.filename = [decoder decodeObjectForKey:@"filename"];
    self.realfilename = [decoder decodeObjectForKey:@"realfilename"];
    self.realfiletype = [decoder decodeObjectForKey:@"realfiletype"];
    self.data = [decoder decodeObjectForKey:@"data"];
    
    return self;
}
@end

@interface AYAIIdentity : NSObject <NSCoding>
@property (readwrite, nonatomic) BOOL isArchive;
@property (strong, nonatomic) NSString *personEmail;
@property (strong, nonatomic) NSString *personFirstName;
@property (strong, nonatomic) NSString *personLastName;
@property (strong, nonatomic) NSString *personAddressCity;
@property (strong, nonatomic) NSString *password;
@property (strong, nonatomic) NSData *subjectPKCS12;
- (id)initWithCoder:(NSCoder *)decoder;
@end

@implementation AYAIIdentity
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
    self.isArchive = [decoder decodeBoolForKey:@"isArchive"];
    self.personEmail = [decoder decodeObjectForKey:@"personEmail"];
    self.personFirstName = [decoder decodeObjectForKey:@"personFirstName"];
    self.personLastName = [decoder decodeObjectForKey:@"personLastName"];
    self.personAddressCity = [decoder decodeObjectForKey:@"personAddressCity"];
    self.password = [decoder decodeObjectForKey:@"password"];
    self.subjectPKCS12 = [decoder decodeObjectForKey:@"subjectPKCS12"];
    
    return self;
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        
        if (argc < 4)
        {
            NSLog(@"Syntax: AYAI30Decryptor <password> <iterations> <filein> [ <pathout> ]\n");
            exit(0);
        }
        const char *password = (const char *)argv[1];
        const char *iterations = (const char *)argv[2];
        const char *filein = (const char *)argv[3];
        const char *pathout = (const char *)argv[4];
        
        unsigned char *cryptBuf;
        struct stat s;
        
        int fdin = open(filein, O_RDONLY);
        if (fdin < 0 ) return EXIT_FAILURE;
        fstat(fdin, &s);
        cryptBuf = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fdin, 0);
        size_t cryptLen = s.st_size;
        
        if (cryptBuf == (void*)-1) return EXIT_FAILURE;
        unsigned char *plainBuf = malloc(s.st_size);
        
        int plainLen = 0;
        int resLen = 0;
        unsigned char key[32], iv[32];
        
        SSLeay_add_all_algorithms();
        SSLeay_add_all_ciphers();
        SSLeay_add_all_digests();
        
        // derive AES 128bit CBC key and IV with SHA256 from password
        // no salt
        // 10 or 100.000 iterations (only these are supported in AYAI 3.0.0)
        EVP_BytesToKey(EVP_aes_128_cbc(), EVP_sha256(), 0,
                       (const unsigned char *)password, (int)strlen(password),
                       (int)atoi(iterations), key, iv);
        EVP_CIPHER_CTX decAESCTX;
        EVP_CIPHER_CTX_init(&decAESCTX);
        EVP_DecryptInit_ex(&decAESCTX, EVP_aes_128_cbc(), NULL, key, iv);
        EVP_DecryptInit_ex(&decAESCTX, NULL, NULL, NULL, NULL);
        EVP_DecryptUpdate(&decAESCTX, plainBuf, &plainLen, cryptBuf, (int)cryptLen);
        EVP_DecryptFinal_ex(&decAESCTX, plainBuf+plainLen, &resLen);
        
        if (strncmp((const char *)plainBuf, "bplist", 6))
        {
            NSLog(@"ERROR: Cannot decrypt AYAI file to object. Wrong password?\n");
            exit(-1);
        }
        close(fdin);
        
        NSData *plainData = [[NSData alloc] initWithBytes:plainBuf
                                                   length:(NSInteger)(plainLen + resLen)];
        NSObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:plainData];

        if ([object isKindOfClass:[AYAIAttachment class]])
        {
            AYAIAttachment *attachment = (AYAIAttachment *)object;

            NSLog(@"Successfully decrypted AYAIAttachment file:\n"
                  "          comment: %@\n"
                  "         filename: %@\n"
                  "     realfilename: %@\n"
                  "     realfiletype: %@\n",
                  attachment.comment,
                  attachment.filename,
                  attachment.realfilename,
                  attachment.realfiletype);
            
            if (pathout)
            {
                char *fileout = malloc(strlen(pathout) + strlen([attachment.realfilename UTF8String]) + 2);
                sprintf(fileout, "%s/%s", pathout, [attachment.realfilename UTF8String]);
                int fdout = open(fileout, O_CREAT|O_RDWR, 0600);
                if (fdout < 0 ) return EXIT_FAILURE;
                write(fdout, [attachment.data bytes], [attachment.data length]);
                close(fdout);
                NSLog(@"Successfully created file: %s", fileout);
            }
        }
        else if ([object isKindOfClass:[AYAIIdentity class]])
        {
            AYAIIdentity *identity = (AYAIIdentity *)object;

            NSLog(@"Successfully decrypted AYAIIdentity file:\n"
                  "        isArchive: %hhd\n"
                  "      personEmail: %@\n"
                  "  personFirstName: %@\n"
                  "   personLastName: %@\n"
                  "personAddressCity: %@\n"
                  "         password: %@\n",
                  identity.isArchive,
                  identity.personEmail,
                  identity.personFirstName,
                  identity.personLastName,
                  identity.personAddressCity,
                  identity.password);

            if (pathout)
            {
                char *fileout = malloc(strlen(pathout) + strlen([identity.personEmail UTF8String]) + 6);
                sprintf(fileout, "%s/%s.pfx", pathout, [identity.personEmail UTF8String]);
                int fdout = open(fileout, O_CREAT|O_RDWR, 0600);
                if (fdout < 0 ) return EXIT_FAILURE;
                write(fdout, [identity.subjectPKCS12 bytes], [identity.subjectPKCS12 length]);
                close(fdout);
                NSLog(@"Successfully created file: %s", fileout);
            }
        }

        return 0;
    }
}

 

AYAI 2.0 Decrypter Reference Code

Same, but for previous version with S/MIME identities only.


//
//  main.c
//  AYAI20Decryptor
//
//  Copyright (c) 2014 Stephan André. All rights reserved.
//

#import &lt;Foundation/Foundation.h&gt;
#include &lt;string.h&gt;
#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/uio.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;unistd.h&gt;
#include &lt;fcntl.h&gt;
#import &lt;openssl/evp.h&gt;
#import &lt;openssl/err.h&gt;
#import "openssl/aes.h"

@interface AYAIIdentity : NSObject &lt;NSCoding&gt;
@property (readwrite, nonatomic) BOOL isArchive;
@property (strong, nonatomic) NSString *personEmail;
@property (strong, nonatomic) NSString *personFirstName;
@property (strong, nonatomic) NSString *personLastName;
@property (strong, nonatomic) NSString *personAddressCity;
@property (strong, nonatomic) NSString *password;
@property (strong, nonatomic) NSData *subjectPKCS12;
- (id)initWithCoder:(NSCoder *)decoder;
@end

@implementation AYAIIdentity
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
    self.isArchive = [decoder decodeBoolForKey:@"isArchive"];
    self.personEmail = [decoder decodeObjectForKey:@"personEmail"];
    self.personFirstName = [decoder decodeObjectForKey:@"personFirstName"];
    self.personLastName = [decoder decodeObjectForKey:@"personLastName"];
    self.personAddressCity = [decoder decodeObjectForKey:@"personAddressCity"];
    self.password = [decoder decodeObjectForKey:@"password"];
    self.subjectPKCS12 = [decoder decodeObjectForKey:@"subjectPKCS12"];

    return self;
}
@end

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        if (argc != 3)
        {
            NSLog(@"Syntax: AYAI20Decryptor &lt;id-password&gt; &lt;id-file&gt;\n");
            exit(0);
        }
        const char *idpassword = (const char *)argv[1];
        const char *filein = (const char *)argv[2];

        unsigned char cryptBuf[10000];
        unsigned char plainBuf[10000];
        int findes = open(filein, O_RDONLY);
        size_t cryptLen = read(findes, &amp;cryptBuf, sizeof(cryptBuf));
        close(findes);

        int plainLen = 0;
        int resLen = 0;
        unsigned char key[32], iv[32];

        SSLeay_add_all_algorithms();
        SSLeay_add_all_ciphers();
        SSLeay_add_all_digests();

        // derive AES 128bit CBC key and IV with SHA256 from password,
        // no salt, 10 iterations
        EVP_BytesToKey(EVP_aes_128_cbc(), EVP_sha256(), 0,
                       (const unsigned char *)idpassword, (int)strlen(idpassword),
                       10, key, iv);
        EVP_CIPHER_CTX decAESCTX;
        EVP_CIPHER_CTX_init(&amp;decAESCTX);
        EVP_DecryptInit_ex(&amp;decAESCTX, EVP_aes_128_cbc(), NULL, key, iv);
        EVP_DecryptInit_ex(&amp;decAESCTX, NULL, NULL, NULL, NULL);
        EVP_DecryptUpdate(&amp;decAESCTX, plainBuf, &amp;plainLen, cryptBuf, (int)cryptLen);
        EVP_DecryptFinal_ex(&amp;decAESCTX, plainBuf+plainLen, &amp;resLen);

        if (strncmp((const char *)plainBuf, "bplist", 6))
        {
            NSLog(@"ERROR: Cannot decrypt AYAI file to object. Wrong password?\n");
            exit(-1);
        }

        NSData *plainData = [[NSData alloc] initWithBytes:plainBuf
        		length:(NSInteger)(plainLen + resLen)];
        AYAIIdentity *identity = (AYAIIdentity *)[NSKeyedUnarchiver
        		unarchiveObjectWithData:plainData];
        NSLog(@"Successfully decrypted AYAI file:\n"
              "isArchive:%hhd\n"
              "personEmail:%@\n"
              "personFirstName:%@\n"
              "personLastName:%@\n"
              "personAddressCity:%@\n"
              "password:%@\n"
              "subjectPKCS12:\n%@\n",
              identity.isArchive,
              identity.personEmail,
              identity.personFirstName,
              identity.personLastName,
              identity.personAddressCity,
              identity.password,
              identity.subjectPKCS12);

        return 0;
    }
}

 

OpenSSL Sample Code for RSA and X.509

For those who work on a similar project, here´s some snippets from AYAI dealing with RSA key and X.509 certificate generation with OpenSSL. It´s just for illustration purposes, not for copy and paste.


//
//  OpenSSL sample: generate RSA keys and X.509 certificates
//
//  Copyright (c) 2014 Stephan André. All rights reserved.
//

SSLeay_add_all_algorithms();
SSLeay_add_all_ciphers();
SSLeay_add_all_digests();

EVP_PKEY *evpIssuer = EVP_PKEY_new();
X509 *x509Issuer = [generateIssuer:&amp;evpIssuer:self.personEmail:self.personFirstName:self.personLastName:self.personAddressCity];
if (x509Issuer == NULL)
{
    // user canceled key generation
    return;
}
self.issuerX509 = [NSData x509Data:x509Issuer];

EVP_PKEY *evpSubject = EVP_PKEY_new();
X509 *x509Subject = [generateSubject:&amp;evpSubject:evpIssuer:self.personEmail:self.personFirstName:self.personLastName:self.personAddressCity];
if (x509Subject == NULL)
{
    // user canceled key generation
    return;
}
self.subjectX509 = [NSData x509Data:x509Subject];
self.password = [self newRandomPassword];
self.subjectPKCS12 = [generatePFX:self.password:self.personEmail:evpSubject:x509Subject:x509Issuer];


typedef void (^BlockCallback)(int,int);
static void callback(int p, int n, void *anon)
{
    BlockCallback theBlock = (__bridge BlockCallback)anon;  // cast the void * back to a block
    theBlock(p, n);                                         // and call the block
}

- (void)genReq:(BlockCallback)progressCallback completionCallback:(void (^)())completionCallback
{
    self.rsa = RSA_new();
    // pass the C wrapper as the function pointer and the block as the callback argument
    self.rsa = RSA_generate_key((int)self.keysize, RSA_F4, callback, (__bridge void *)progressCallback);
    completionCallback();
}

- (X509 *)generateIssuer:(EVP_PKEY **)privateKey :(NSString *)personEmail :(NSString *)personFirstName :(NSString *)personLastName :(NSString *)personAddressCity
{
    if (!privateKey) return NULL;
    
    X509 *x = X509_new();
    EVP_PKEY *pk = *privateKey;
    X509_NAME *name = NULL;

    [self genReq:^(int p, int n)
     {
         [[NSOperationQueue mainQueue] addOperationWithBlock:^{
             float percent = (float)self.keygencbcnt++ / (float)self.keysize * 1000;
             [self.progressBar setProgress:percent/100 animated:YES];
         }];
     }
    completionCallback:^{
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        }];
    }];
    
    if (self.progressBar.tag == 1)
    {
        return NULL;
    }

    EVP_PKEY_assign_RSA(pk,self.rsa);
    
    X509_set_version(x,2);
    
    // serial number is sha1 hash of own mail address, i.e. always the same for an address
    unsigned char md[SHA_DIGEST_LENGTH];
    SHA1((const unsigned char *)[personEmail cStringUsingEncoding:NSASCIIStringEncoding], [personEmail length], md);
    BIGNUM *bn = BN_bin2bn((const unsigned char *)&amp;md, SHA_DIGEST_LENGTH, NULL);
    ASN1_INTEGER *serial = BN_to_ASN1_INTEGER(bn, NULL);
    X509_set_serialNumber(x, serial);
    
    X509_gmtime_adj(X509_get_notBefore(x),0);
    X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*365*10);
    X509_set_pubkey(x,pk);
    
    name = X509_get_subject_name(x);
    X509_NAME_add_entry_by_txt(name,"O",
                               MBSTRING_ASC, (const unsigned char *)"And You And I", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"OU",
                               MBSTRING_ASC, (const unsigned char *)[LS("X509IssuerOU") UTF8String], -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"L",
                               MBSTRING_UTF8, (const unsigned char *)[personAddressCity UTF8String], -1, -1, 0);
    NSString *tout = [[NSString alloc] initWithFormat:LS("X509IssuerFormatCN"),
                      personFirstName, personLastName];
    X509_NAME_add_entry_by_txt(name,"CN",
                               MBSTRING_UTF8, (const unsigned char *)[tout UTF8String], -1, -1, 0);
    X509_set_issuer_name(x,name);
    
    tout = [[NSString alloc] initWithFormat:@"email:%@", personEmail];
    X509_EXTENSION *ex;
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "critical,keyCertSign");
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, "critical,CA:TRUE");
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, (char *)[tout UTF8String]);
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    
    X509_sign(x,pk,EVP_sha1());
    
    return x;
}

- (X509 *)generateSubject:(EVP_PKEY **)privateKey :(EVP_PKEY *)issuerKey :(NSString *)personEmail :(NSString *)personFirstName :(NSString *)personLastName :(NSString *)personAddressCity
{
    if (!privateKey) return NULL;
    
    X509 *x = X509_new();
    EVP_PKEY *pk = *privateKey;
    X509_NAME *name = NULL;

    [self genReq:^(int p, int n)
     {
         [[NSOperationQueue mainQueue] addOperationWithBlock:^{
             float percent = (float)self.keygencbcnt++ / (float)self.keysize * 1000;
             [self.progressBar setProgress:percent/100 animated:YES];
         }];
     }
    completionCallback:^{
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        }];
    }];
    
    if (self.progressBar.tag == 1)
    {
        return NULL;
    }
    
    EVP_PKEY_assign_RSA(pk,self.rsa);
    X509_set_version(x,2);
    
    // serial number is double sha1 hash of own mail address, i.e. always the same for an address
    unsigned char md[SHA_DIGEST_LENGTH];
    SHA1((const unsigned char *)[personEmail cStringUsingEncoding:NSASCIIStringEncoding], [personEmail length], md);
    SHA1(md, SHA_DIGEST_LENGTH, md);
    BIGNUM *bn = BN_bin2bn((const unsigned char *)&amp;md, SHA_DIGEST_LENGTH, NULL);
    ASN1_INTEGER *serial = BN_to_ASN1_INTEGER(bn, NULL);
    X509_set_serialNumber(x, serial);
    
    X509_gmtime_adj(X509_get_notBefore(x),0);
    X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*365*10);
    X509_set_pubkey(x,pk);
    
    name = X509_get_subject_name(x);
    X509_NAME_add_entry_by_txt(name,"O",
                               MBSTRING_ASC, (const unsigned char *)"And You And I", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"OU",
                               MBSTRING_ASC, (const unsigned char *)[LS("X509SubjectOU") UTF8String], -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"L",
                               MBSTRING_UTF8, (const unsigned char *)[personAddressCity UTF8String], -1, -1, 0);
    NSString *tout = [[NSString alloc] initWithFormat:@"%@ %@",
                      personFirstName, personLastName];
    X509_NAME_add_entry_by_txt(name,"CN",
                               MBSTRING_UTF8, (const unsigned char *)[tout UTF8String], -1, -1, 0);
    name = X509_get_issuer_name(x);
    X509_NAME_add_entry_by_txt(name,"O",
                               MBSTRING_ASC, (const unsigned char *)"And You And I", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"OU",
                               MBSTRING_ASC, (const unsigned char *)[LS("X509IssuerOU") UTF8String], -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"L",
                               MBSTRING_UTF8, (const unsigned char *)[personAddressCity UTF8String], -1, -1, 0);
    tout = [[NSString alloc] initWithFormat:LS("X509IssuerFormatCN"),
            personFirstName, personLastName];
    X509_NAME_add_entry_by_txt(name,"CN",
                               MBSTRING_UTF8, (const unsigned char *)[tout UTF8String], -1, -1, 0);
    
    tout = [[NSString alloc] initWithFormat:@"email:%@", personEmail];
    X509_EXTENSION *ex;
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, "critical,digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment");
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, (char *)[tout UTF8String]);
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    ex = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, "emailProtection");
    X509_add_ext(x, ex, -1);
    X509_EXTENSION_free(ex);
    
    X509_sign(x,issuerKey,EVP_sha1());
    
    return x;
}

- (NSData *)generatePFX:(NSString *)password :(NSString *)personEmail :(EVP_PKEY *)subjectKey :(X509 *)subjectCert :(X509 *)issuerCert
{
    STACK_OF(X509)  *caCertStack;
    PKCS12          *p12;
    BIO             *bpout = BIO_new(BIO_s_mem());
    
    caCertStack = sk_X509_new_null();
    sk_X509_push(caCertStack, issuerCert);
    p12 = PKCS12_create(
                        (char *)[password cStringUsingEncoding:NSISOLatin1StringEncoding],  // certbundle access password
                        (char *)[personEmail cStringUsingEncoding:NSASCIIStringEncoding], // friendly certname
                        subjectKey,   // the certificate private key
                        subjectCert,  // the main certificate
                        caCertStack, // stack of CA cert chain
                        0,           // int nid_key (default NID_pbe_WithSHA1And3_Key_TripleDES_CBC)
                        0,           // int nid_cert NID_pbe_WithSHA1And40BitRC2_CBC)
                        PKCS12_DEFAULT_ITER,    // int iter (default 2048)
                        PKCS12_DEFAULT_ITER,    // int mac_iter (default 1)
                        0                       // int keytype (default no flag
                        );
    i2d_PKCS12_bio(bpout, p12);
    
    char *outputBuffer;
    long outputLength = BIO_get_mem_data(bpout, &amp;outputBuffer);
    NSData * newP12Data = [[NSData alloc] initWithBytes:outputBuffer length:outputLength];

    sk_X509_free(caCertStack);
    PKCS12_free(p12);
    BIO_free_all(bpout);
    
    return newP12Data;
}

Advertisements