• 异或的4种堪称神奇的运用场景


    简介

    众所周知,编程语言一般都内置了3种位运算符&(AND)|(OR)~(NOT),用来实现位运算,但其实还有一种非常常用的位运算,即异或^(XOR),数学中常用⊕表示。

    异或的运算逻辑如下:
    1 ⊕ 1 = 0
    1 ⊕ 0 = 1
    0 ⊕ 1 = 1
    0 ⊕ 0 = 0

    简单来说,异或的特性是,两个值相同,结果为0,不同则结果为1,所以才叫异或嘛,两个值相异再或起来,不就是1嘛😂

    由于异或特殊的运算特性,使其可以实现一些神奇的操作,如下:

    1. 实现加减法
    2. 加解密
    3. 密钥交换
    4. 数据备份

    那就来一起看看吧!

    运算定律

    1. 任何值与自身异或,结果为0
    1. x ^ x = 0
    2. 复制代码
    1. 任何值与0异或,结果为其自身
    1. x ^ 0 = x
    2. 复制代码
    1. 交换律
    1. x ^ y ^ z = x ^ z ^ y
    2. 复制代码
    1. 结合律
    1. x ^ y ^ z = x ^ (y ^ z)
    2. 复制代码

    异或的运算定律比较简单,就不写数学证明了,感兴趣可到网上搜索。

    实现加减法

    XOR的第一种运用场景就是实现加减法,在我们上小学时,应该都学过进位加法退位减法来做加减运算,咱们来复习一下吧。

    • 进位加法:
      当某一位上两数之和大于10时,需要进位,即高位上要多加一个1。

    • 退位减法:
      当某一位上两数相减不够时,需要退位,即从高位上借(减)一个1,来当作10用。

    异或其实与这个类似,不过它不会产生进位与退位,如下:

    1. 如二进制加法01 + 01 = 10,而异或运算是01 ^ 01 = 00,它其实做了加法,但高位不进位,所以异或又称不进位加法
    2. 如二进制减法10 - 01 = 01,而异或运算是10 ^ 01 = 11,也可以看做个位0向高位借了一个1当2来用,但高位不减1,所以异或也可以称为不退位减法

    利用异或的这个特性,只要再解决一下进位和退位问题,就可以实现加减法了,如下是java代码实现:

    1. public static int intPlus(int a, int b){
    2. while (b != 0){
    3. // 加法(未考虑进位)
    4. int sum = a ^ b;
    5. // 进位值,二进制中1 + 1的场景才会进位,a & b只有两个都为1,结果才是1,左移一位,就是进位值了
    6. int addition = (a & b) << 1;
    7. // 赋值,下次循环将进位加到a里面来,直到进位等于0
    8. a = sum;
    9. b = addition;
    10. }
    11. return a;
    12. }
    13. public static int intSubtract(int a, int b){
    14. while (b != 0){
    15. // 减法(未考虑退位)
    16. int sum = a ^ b;
    17. // 退位值,二进制中0 - 1的场景才会退位,~a & b只有a=0,b=1,结果才是1,左移一位,就是退位值了
    18. int abdication = (~a & b) << 1;
    19. // 赋值,下次循环将退位再从a中减掉,直到退位等于0
    20. a = sum;
    21. b = abdication;
    22. }
    23. return a;
    24. }
    25. 复制代码

    这也是为什么CPU里面都是做位运算的逻辑门电路,却能实现数值计算😱

    加解密

    使用XOR可以实现简单的加解密,假如明文为plain,密钥为key,密文为secret,则:

    1. // 加密
    2. secret = plain ^ key
    3. // 解密
    4. plain = secret ^ key
    5. 复制代码

    为什么一定能解密呢,如下:

    1. secret ^ key
    2. = (plain ^ key) ^ key // 代入加密表达式
    3. = plain ^ (key ^ key) // 代入结合律
    4. = plain ^ 0 // 任何值与自身异或等于0
    5. = plain // 任何值与0异或等于自身
    6. 复制代码

    java实现如下:

    1. public static byte[] xorEncrypt(byte[] plain, byte[] key){
    2. byte[] secret = new byte[plain.length];
    3. for(int i = 0; i < plain.length; i++){
    4. secret[i] = (byte) (plain[i] ^ key[i % key.length]);
    5. }
    6. return secret;
    7. }
    8. public static byte[] xorDecrypt(byte[] secret, byte[] key){
    9. return xorEncrypt(secret, key);
    10. }
    11. public static void main(String[] args) {
    12. byte[] plain = "hello xor".getBytes(StandardCharsets.UTF_8);
    13. byte[] key = "1234".getBytes(StandardCharsets.UTF_8);
    14. byte[] secret = xorEncrypt(plain, key);
    15. byte[] plain2 = xorDecrypt(secret, key);
    16. // 输出 plain:aGVsbG8geG9y,secret:WVdfWF4SS1tD, plain2:aGVsbG8geG9y
    17. System.out.printf("plain:%s,secret:%s, plain2:%s", Base64.encode(plain), Base64.encode(secret),
    18. Base64.encode(plain2));
    19. }
    20. 复制代码

    密钥交换

    密钥交换就是通信双方需要将加密密钥发送给对方,但不让中间的信道监听者知道密钥是什么,而利用XOR就可以实现一种最简单的密钥交换方案,如下:

    1. 客户端生成一个随机密钥p,然后再使用自己的密钥k1对其XOR加密,将加密后的s1发送给服务端,即s1 = p ^ k1。
    2. 服务端再使用自己的密钥k2对s1做XOR加密,将加密后的s2回复给客户端,即s2 = s1 ^ k2。
    3. 客户端再使用自己的密钥k1对s2做XOR解密,将解密后的s3回复给服务端,即s3 = s2 ^ k1。
    4. 服务端再使用自己的密钥k2对s3做XOR解密,即s4 = s3 ^ k2,而按XOR的性质,s4会等于p,即客户端顺利将p发送给了服务端,且中间通信数据都是加密的。


      整个过程可以看做先是双方都在密文中做了一次加密,而后双方又逐步解开。

    证明其正确性也很简单,只需要将式子代入一下即可,如下:

    1. s4 = s3 ^ k2
    2. = (s2 ^ k1) ^ k2
    3. = ((s1 ^ k2) ^ k1) ^ k2
    4. = (((p ^ k1) ^ k2) ^ k1) ^ k2
    5. = p ^ k1 ^ k2 ^ k1 ^ k2 // 应用结合律
    6. = p ^ (k1 ^ k2) ^ (k1 ^ k2) // 再应用结合律,把k1 ^ k2看成整体,就是加密之后再解密了
    7. = p
    8. //也可以这样证明
    9. = p ^ k1 ^ k2 ^ k1 ^ k2
    10. = p ^ (k1 ^ k1) ^ (k2 ^ k2) // 应用交换律
    11. = p ^ 0 ^ 0 // 应用自身与自身异或为0
    12. = p // 应用任何值与0异或为其自身
    13. 复制代码

    数据备份

    使用XOR也可以很容易实现多个数据的互备,如下:
    假如有数据a、b、c,则z = a ^ b ^ c,然后把数据z备份下来。

    • 当a丢失时,可使用z ^ b ^ c来恢复。
    • 当b丢失时,可使用z ^ a ^ c来恢复。
    • 当c丢失时,可使用z ^ a ^ b来恢复。

    这真是太神奇了,备份了一份数据z后,丢失了任何一份数据,都可以通过数据z与其它数据一起恢复回来😎,而磁盘阵列技术raid5的数据备份原理,就是用的这个特性。

    实现异或

    由于在布尔代数中,其实只需要与、或、非运算就可以实现所有其它运算,所以异或其实也是可以通过与、或、非实现的,最直观的实现方式如下:

    1. a ^ b = (~a & b) | (a & ~b)
    2. 复制代码

    ok,异或的使用场景就介绍到这了,还有没有其它神奇的运用场景呢?

  • 相关阅读:
    python—类的组成、对象的创建、面向对象三大特征
    (02)Cartographer源码无死角解析-(02) ROS基础讲解→
    用PyTorch简单实现线性回归
    【逆向】通过新增节移动导出表和重定位表(附完整代码,直接可运行)
    顺为资本许达来:判断风口一看时间点,二看商业本质
    HummerRisk V0.5.2:升级对象存储、云检测、云审计和K8s资源态势等
    Qt 之悬浮球菜单
    HackTheBox Photobomb 命令注入与远程代码执行和环境变量提权
    【大数据】-- flink kubernetes operator 入门与实践
    MySQL 默认隔离级别是RR,为什么阿里等大厂会改成RC?
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126816790