sqlite3, arm64 et moi
Hello tout le monde !!
Je me permets de poster une petite question ici parce que Google m'a lâchement abandonné, lui qui est toujours si plein de réponses, de fraicheur et de volupté.
Il y a actuellement une application en ligne sur l'Appstore qui a été packagée et publiée il y a un moment. Je pense que vous êtes tous au fait qu'à partir de février 2015, toutes les applications doivent être compatibles pour les devices en 64 bits, et donc qu'il faut "rajouter" le petit "arm64", aKa $(ARCHS_STANDARD) dans les architectures valides !
Bien évidemment, ce n'était pas le cas lorsque cette application (que je n'ai pas faite) a été publiée.
Je fais donc la mise à jour, et je teste sur mon device. AUCUN PROBLàˆME tout roule !
Pourquoi je suis ici alors ? Et bien il y a un cas qui me pose problème.
C'est quand l'application a été installé via l'app store (compilée en 32bits), puis que la mise à jour soit faite (avec la nouvelle architecture). Au lancement de l'application : BOUM, un crash direct en bonne et due forme.
Voici ce que je découvre dans mes logs :
SQLite error (26) : file is encrypted or is not a database [0x0000001a]
J'ai un peu creusé (et je continue encore d'ailleurs) et il semblerait que la fonction qui génère cette erreur soit celle ci :
sqlite3_prepare_v2()
Ma question est la suivante : est-ce que lors du passage sur une architecture 64bits il y a quelque chose à faire pour que sqlite3 ne pose pas ce genre de problème ?
Je vous montre la fonction qui permet de préparer la requête :
// Prepare query
- (SwingBool) prepareQuery : (SwingString *)query
{
// Reset columns name dictionary
[self _dictColIndexReset];
// Query format for SQLite
const char *sqliteQuery = [query UTF8String];
// SQLite Error code
SwingInteger sqliteError;
// Reset first fetch flag
_firstFetchDone = NO;
// Prepare query
sqliteError = sqlite3_prepare_v2( _database, sqliteQuery, -1, &_statement, NULL );
if ( sqliteError == SQLITE_OK )
{
return YES;
}
else
{
RAISE_EXCEPTION_SQLITE(sqliteError,sqliteQuery);
return NO;
}
}
La base en elle-même est bien cryptée et j'ai vérifié que je récupère bien la clé "d'encryption". Je ne vois pas ce qui a pu changer juste en changeant l'architecture...
Merci d'avance pour toute aide et suggestions que vous pourrez m'apporter !
MoKeS
Réponses
Alors non, pas tout à fait. L'architecture arm64 doit être présente dans les nouvelles soumissions. C'est à dire que si une application 32 bits est déjà sur le store, il n'y a pas d'obligation à la mettre à jour.
Pour ton problème, c'est un cas très particulier, il va falloir refaire la manip (lancer l'ancienne version, créer une base, tester sur la nouvelle version). Bon courage.
La seule raison évidente est que comme la taille des int change, tu aurais un problème avec un int signé.
@Céroce
Merci pour ta réponse rapide !
Du coup j'ai voulu faire simple, tout rebasculer en 32, histoire de pouvoir au moins faire cette mise à jour, mais je me rends compte que même en 32 (armv7) j'ai la même erreur au final.
La conclusion ici est qu'il y a une différence (quelque part ..) entre la version en ligne, et la version actuelle qui a évolué, qui a été mal maintenue et c'est moi maintenant qui me tape ce genre d'histoires ...
Je te remercie pour la précision, et je vais continuer mes investigations. Je publierais la solution lorsque je l'aurais trouvé, ça pourra peut-être servir à quelqu'un d'autre ... un jour ...
MoKeS
Avec quel outil tu cryptes ta base ? SQLCypher ?
@muqaddar
Non SQLCypher n'est pas utilisé. De ce que je vois dans le code, ils ont fait une espèce d'encryption "maison" avec sqlite3 directement et un keychain.
J'avais eu un problème similaire, mais avec SQLCypher.
Est-ce que le fichier encrypté a changé depuis la dernière version ?
Est-il copié dans la sandbox ?
Tu n'as pas le problème en debug sur ton iPhone, y compris si tu compiles l'ancienne version puis la nouvelle sans la supprimer avant ? (ce que fait la MAJ AppStore)
- Les nouvelles applications soumises sur le store doivent obligatoirement avoir le 64 bits depuis février 2015
- Pour les applications qui sont déjà présentes sur le store et que tu souhaites mettre à jour, tu auras jusqu'à juillet (de mémoire) 2015 avant d'être obligé d'y inclure le 64 bits. Autrement dit, si tu mets à jour avant, ça passe encore si tu n'as pas le 64 bits, mais si tu veux faire une mise à jour après juillet 2015 là par contre tu seras obligé que ta mise à jour intègre le 64 bits.
- Si tu ne souhaites pas mettre à jour ton application car tu n'en n'as pas la nécessité, tu n'as pas le besoin de recompiler juste pour ajouter le 64 bits, dans le sens où ton application qui est déjà sur le store ne va pas être retirée du store sous prétexte qu'elle est encore 32 bits. L'obligation est uniquement au moment où tu soumets une app, pas pour les apps déjà soumises pour lesquelles tu ne referas pas de soumission.
Autrement dit si tu as besoin de faire une mise à jour que tu soumettras avant juillet ça passera si tu n'as pas le 64 bits, mais après c'est quand même mieux de le rajouter quand même, car il faudra sûrement que tu le rajoutes un jour si tu comptes faire des mises à jour régulières de ton application et que tu continueras d'en faire après juillet 2015. Va bien falloir y passer, et autant le faire au plus tôt que d'attendre le dernier moment.Genre on a des clients qui sont parfois en rush pour sortir une mise à jour de leur app en un temps restreint, parce que la mise à jour doit être prête pour un évènement à une date clé donc vite vite vite faut absolument ajouter le truc spécifique pour l'évènement et la sortir, c'est pas méchant comme modif ça devrait pas prendre trop de temps... ah oui mais là cette mise à jour vers 64 bits que tu as éternellement repoussée tu ne peux plus reculer devant maintenant et faut la faire, et ça tombe mal car tu es pressé...
Bref même si ce ne sera, pour les mises à jour d'apps existantes, obligatoire qu'en juillet, ça reste une bonne idée de le prévoir quand même dès maintenant.
@muqaddar
En fait le plan de test est le suivant :
Cas 1 :
Cas 2 :
Pour tes autres questions, je ne peux que supputer parce que je ne sais pas quelles ont été les modifications effectuées sur l'application entre Avril 2014 (date de publication sur l'app store) et aujourd'hui.
Ce que j'ai fait au final :
Lorsque je lance l'application, je teste si la base est accessible ou non erronée. Si ce n'est pas le cas, je supprime le fichier de base de donnée et l'application continue comme une première utilisation. Dans mon cas ça ne pose pas de problème, étant donné que les données sont synchronisées sur nos serveurs (à la manière d'un cloud) et on peut se permettre de resynchroniser la base "fraichement".
De cette façon cela est quasi invisible pour l'utilisateur.
On est d'accord que dans le cas où l'utilisateur perd toutes ses données, cette solution n'est pas viable pour un sou, mais je n'ai plus vraiment le choix et le temps me manque.
@AliGator
Merci pour toutes ces précisions !! Je te rejoins la dessus également parce que du coup j'vais faire la mise à jour avec le support 64bits (comme ça c'est fait) et je suis exactement dans ce cas là ! Le client voulait une mise à jour rapide, j'ai perdu 1 jour à faire la conversion 64 bits (à cause notamment d'Itunes Connect qui ne me disait pas pourquoi il refusait mon binaire, mais ça c'est une autre histoire)
Merci encore pour les infos !
On dirait donc que l'application nouvelle n'arrive pas à lire l'ancienne base installée.
Et si tu vires le 64 bits, ça marche ?
--
C'est étrange car j'ai fait de même: j'ai une 32 bits que je mets à jour avec une 64 bits, et je peux lire les anciens fichiers sans problème. Mais j'utilise SQLCipher...
ça a l'air indépendant du 64 ou du 32, parce qu'en ayant rebasculé sur du 32 c'est toujours le même résultat !
Je ne sais pas ce qui a changé entre avril et aujourd'hui :-/
Toi si ton schéma de ta base SQL a changé entre avril et aujourd'hui mais que celui qui a fait le changement n'a pas pris la peine de s'assurer de la retro-compatibilité avec l'ancien schéma tu peux aller le fouetter.
Bon après tu devrais pouvoir retrouver ce qui a changé entre avril et aujourd'hui avec ton historique GIT. (Tu pourrais meme utiliser "git bisect" pour trouver plus rapidement par dicothomie depuis quel commit ce problème de non-rétro-compatibilité a été introduit)
Je ne pense pas que ce soit un problème de schéma.
Regarde aussi de quand date la dernière modification du fichier, et regarde le comportement avec les anciennes versions.
... c'est pas faux.
Est-ce que tu as essayé de regénérer le fichier sqlite ?
J'ai eu un problème très con une fois.
De mémoire: je regénère mon fichier encrypté avec un projet Xcode à part (Mac app), tout en laissant le projet Xcode principal de l'application ouvert. Les 2 utilisaient la même base SQLite non chiffrée avant chiffrement.
Si le projet principal était ouvert, le chiffrement se faisait, mais "mal". J'avais la même erreur que toi.
Il fallait que je ferme le projet Xcode principal pour le chiffrer avec l'autre.
Es tu certain d'avoir la bonne clé ? Il y a peut-être une clé en debug et une autre en release.
Pour en avoir le coeur net, si tu n'es pas certain d'avoir les sources, il va falloir chercher la clé dans le binaire de l'ancienne version.
Mais, en fait, si la clé est dans le keychain d'iOS, tu dois pouvoir la récupérer avec la nouvelle version.
Je vois bien l'intérêt de la mettre dans le keychain d'iOS, sauf que pour la mettre, il faut bien qu'elle soit copiée depuis un autre fichier au premier lancement de l'app par exemple ? Je vois donc mal où on gagne en sécurité.
Quelle est la bonne pratique ? Dans quel fichier mettre cette clé ?
Je ne suis pas un spécialiste de ces questions là , mais effectivement si la clé est stockée dans le binaire de l'app, ce n'est pas idéal.
Je ne crois pas qu'il puisse y avoir de bonne méthode à partir du moment où la base chiffrée est fournie avec l'app, et donc toujours chiffrée avec la même clé.
Eventuellement, en téléchargeant la clé au premier lancement (via un protocol sécurisé) puis en la stockant directement dans le keychain, mais du coup cela oblige à avoir une connexion réseau au moment du premier lancement.
C'est évidemment possible uniquement si l'application démarre avec une base entièrement vide.
Voilà !
Moi j'ai 15 tables pleines au démarrage.
Exactement. Donc pas idéal non plus.
En fait il faudrait pouvoir intervenir au niveau de l'app store pour avoir un téléchargement personnalisé avec une clé généré pour l'occasion et préchargée dans le keychain.
Un système un peu similaire à ce qu'il faut mettre en place pour décoder les receipts mais avec les données de l'application.
Ca ne servirait à quelquechose que si en plus de ça, l'AppStore chiffrait aussi les données de ta base dynamiquement avec cette clé avant de te la télécharger. Ce qui violerait la signature de l'application en modifiant le contenu du bundle.
Oui c'est ce que je voulais dire. Il faudrait que les données soit chiffrés par l'app store, ce qui parait très compliqué à mettre en place.
Par contre, si on revient à une seule clé de chiffrage pour tous les utilisateurs, on pourrait avoir un système où Apple nous permettrait de placer cette clé dans le keychain de l'app dès l'installation.
Le développeur fournirait un plist contenant les clés à inclure dans le keychain au moment de l'installation.
Cela pourrait aussi être utile pour toutes les apps qui utilisent des API HTTPS qui ont besoin d'un APP TOKEN.
Mais bon comme tout problème de sécurité de ce genre, quand on pense avoir bouché un trou dans la chaà®ne de traitement, souvent on l'a fait en introduisant une autre faille autre part, et on n'a fait que repousser le problème... il n'y a pas de solution miracle.