Ceci fait partie du code source de l'utilitaire de cryptage de fichiers BCrypt. Inchangé, sauf quelques commentaires que j'ai ajoutés.
uLong BFEncrypt(char **input, char *key, uLong sz, BCoptions *options) {
uInt32 L, R;
uLong i;
BLOWFISH_CTX ctx;
int j;
unsigned char *myEndian = NULL;
j = sizeof(uInt32);
getEndian(&myEndian);
// makes space 2 bytes
memmove(*input+2, *input, sz);
// add endian and compresssion option
memcpy(*input, myEndian, 1);
memcpy(*input+1, &options->compression, 1);
sz += 2; /* add room for endian and compress flags */ // total size increased
Blowfish_Init (&ctx, key, MAXKEYBYTES); // initialize
// encrypt the file
for (i = 2; i < sz; i+=(j*2)) { /* start just after tags */
memcpy(&L, *input+i, j);// copy j bytes from input to L
memcpy(&R, *input+i+j, j); // copy second j byte to R
Blowfish_Encrypt(&ctx, &L, &R); // encrypt
memcpy(*input+i, &L, j); // copy everything back
memcpy(*input+i+j, &R, j);
}
if (options->compression == 1) {
if ((*input = realloc(*input, sz + j + 1)) == NULL)
memerror();
memset(*input+sz, 0, j + 1);
memcpy(*input+sz, &options->origsize, j);
sz += j; /* make room for the original size */
}
free(myEndian);
return(sz);
}
Dans la boucle, nous copions d'abord le tampon de fichier octet par octet vers de nouvelles variables, puis nous appliquons le cryptage Blowfish. Et puis à nouveau en copiant les octets dans le tampon. Pourquoi ne puis-je pas passer d'octets directement à la fonction de cryptage? Pourquoi memcpy()
est même nécessaire?
Pourquoi ne puis-je pas passer d'octets directement à la fonction de cryptage?
Il y a deux règles contre cela, ou du moins ne pas l'appuyer.
La première est que la conversion d'un pointeur en char
pointeur en un int
a un comportement indéfini si l'alignement n'est pas correct pour an int
et, même si l'alignement est correct, la valeur du résultat n'est pas entièrement définie. Les règles à ce sujet se trouvent dans C 2018 6.3.2.3, qui couvre les conversions de pointeurs.
Généralement, les objets tels que int
doivent être situés à des multiples de quatre octets. Ceci est dû à la façon dont la mémoire de l'ordinateur et le bus de données sont organisés; les différents «fils» impliqués sont mis en place pour transférer les choses en groupes de certaines tailles et alignements. Lorsque le compilateur d'un tel système génère des instructions pour travailler avec des int
objets, il génère des instructions qui chargent des mots alignés. Si vous prenez un char
pointeur qui n'est pas aligné et que vous le convertissez en pointeur vers int
, certains processeurs génèrent une interruption lorsqu'une instruction de mot aligné de charge tente d'utiliser une adresse non alignée. D'autres processeurs peuvent ignorer les bits bas de l'adresse et charger un mot aligné à partir d'une adresse différente.
Même si l'adresse est alignée, la norme C ne garantit pas le résultat de la conversion de a char *
en un int *
pointant réellement vers le même endroit que l'original. En effet, dans certains systèmes, pour la plupart archaïques maintenant, les pointeurs vers différents types étaient représentés de différentes manières. Certains systèmes accèdent à la mémoire uniquement en mots de plusieurs octets, donc, pour implémenter char *
, un compilateur doit synthétiser des adresses différentes des adresses matérielles, alors que, pour int *
, un compilateur peut utiliser l'adresse matérielle directement.
La deuxième règle est que la mémoire désignée pour être utilisée pour un type, tel qu'un tableau de char
, ne peut pas être librement utilisée comme un autre type, tel que int
. Cette règle se trouve dans C 2018 6.5 7. Elle a des situations spécifiques qui sont autorisées, telles que n'importe quel type, tel que int
ou float
, peut être consulté en tant que char
, mais pas l'inverse. Le but de cette règle est qu'une routine ayant passé un int *i
et un float *f
peut savoir que dans un code comme celui-ci:
for (int j = 0; j < 1024; ++j)
f[j] += *i;
le f[j]
accède toujours à a float
et n'accède jamais à un int
, donc le corps de cette boucle ne change jamais la valeur de *i
. Cela signifie que le compilateur peut optimiser le code pour:
int t = *i;
for (int j = 0; j < 1024; ++j)
f[j] += t;
qui sauve le travail de chargement répété *i
de la mémoire, parce que l'objet temporaire t
peut être conservé dans un registre de processeur. (En plus de cela, le compilateur pourrait effectivement utiliser float t = *i;
, économisant à la fois le chargement répété de *i
depuis la mémoire et la conversion répétée en float
pour l'ajout.)
Vous pouvez regarder cette motivation, puis regarder Blowfish_Encrypt
et voir que BlowFish_Encrypt
cette optimisation potentielle ne profite jamais de cette optimisation potentielle, peut-être parce qu'elle ne fonctionne jamais avec des types mixtes qui seraient affectés par cette règle. Cependant, la complexité des optimisations du compilateur devient plus difficile à voir à mesure que les compilateurs deviennent de plus en plus avancés et agressifs dans leurs transformations, il est donc facile de manquer un avantage que le compilateur tirera de la règle sur l'aliasing d'un type comme un autre. Dans tous les cas, comme la règle existe, vous n'avez aucune garantie que votre programme fonctionnera si vous la violez.
Cet article est collecté sur Internet, veuillez indiquer la source lors de la réimpression.
En cas d'infraction, veuillez [email protected] Supprimer.
laisse moi dire quelques mots