XorCipher(String)   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
1
/*
2
 * This file is part of ArakneUtils.
3
 *
4
 * ArakneUtils is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * ArakneUtils is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with ArakneUtils.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2017-2020 Vincent Quatrevieux
18
 */
19
20
package fr.arakne.utils.encoding;
21
22
import org.checkerframework.checker.index.qual.NonNegative;
23
import org.checkerframework.common.value.qual.MinLen;
24
import org.checkerframework.dataflow.qual.Pure;
25
import org.checkerframework.dataflow.qual.SideEffectFree;
26
27
import java.io.UnsupportedEncodingException;
28
import java.net.URLDecoder;
29
import java.net.URLEncoder;
30
import java.nio.charset.StandardCharsets;
31
32
/**
33
 * Implementation of the xor cipher in Dofus 1
34
 * Note: This cipher is not secure, the key can be easily retrieved.
35
 *
36
 * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L297
37
 */
38
public final class XorCipher {
39
    private final @MinLen(1) String key;
40
41 1
    public XorCipher(@MinLen(1) String key) {
42 1
        this.key = key;
43 1
    }
44
45
    /**
46
     * Get the key used by the cipher
47
     *
48
     * @return The key
49
     */
50
    @Pure
51
    public @MinLen(1) String key() {
52 1
        return key;
53
    }
54
55
    /**
56
     * Encrypt the value using the current key
57
     * The encrypted value is an upper case hexadecimal string
58
     *
59
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L297
60
     *
61
     * @param value Value to encrypt
62
     * @param keyOffset Offset to use for the key
63
     *
64
     * @return Encrypted value
65
     */
66
    @SideEffectFree
67
    @SuppressWarnings("cast.unsafe") // XOR will return an int, so cast to char cannot be checked
68
    public String encrypt(String value, @NonNegative int keyOffset) {
69 1
        final String plain = escape(value);
70 1
        final StringBuilder encrypted = new StringBuilder(plain.length() * 2);
71
72 1
        for (int i = 0; i < plain.length(); ++i) {
73 1
            final char c = plain.charAt(i);
74 1
            final char k = key.charAt((i + keyOffset) % key.length());
75
76 1
            final char e = (char) (c ^ k);
77
78 1
            if (e < 16) {
79 1
                encrypted.append('0');
80
            }
81
82 1
            encrypted.append(Integer.toHexString(e));
83
        }
84
85 1
        return encrypted.toString().toUpperCase();
86
    }
87
88
    /**
89
     * Decrypt the value using the current key
90
     *
91
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L314
92
     *
93
     * @param value Value to decrypt. Must be a valid hexadecimal string
94
     * @param keyOffset Offset to use on the key. Must be the same used for encryption.
95
     *
96
     * @return The decrypted value
97
     *
98
     * @throws IllegalArgumentException When an invalid string is given
99
     * @throws NumberFormatException When an invalid hexadecimal string is given
100
     */
101
    @SideEffectFree
102
    @SuppressWarnings({
103
        "cast.unsafe",  // XOR will return an int, so cast to char cannot be checked
104
        "array.access.unsafe.high.range" // i / 2 is not correctly resolved
105
    })
106
    public String decrypt(String value, @NonNegative int keyOffset) {
107 1
        if (value.length() % 2 != 0) {
108 1
            throw new IllegalArgumentException("Invalid encrypted value");
109
        }
110
111 1
        final char[] decrypted = new char[value.length() / 2];
112
113 1
        for (int i = 0; i < value.length() - 1; i += 2) {
114 1
            final char k = key.charAt((i / 2 + keyOffset) % key.length());
115 1
            final char c = (char) Integer.parseInt(value.substring(i, i + 2), 16);
116
117 1
            decrypted[i / 2] = (char) (c ^ k);
118
        }
119
120
        try {
121 1
            return URLDecoder.decode(new String(decrypted), StandardCharsets.UTF_8.toString());
122
        } catch (UnsupportedEncodingException e) {
123
            throw new IllegalArgumentException("Invalid UTF-8 character", e);
124
        }
125
    }
126
127
    /**
128
     * Escape value using URL encode
129
     *
130
     * https://github.com/Emudofus/Dofus/blob/1.29/dofus/aks/Aks.as#L275
131
     */
132
    @SideEffectFree
133
    private static String escape(String value) {
134 1
        final StringBuilder escaped = new StringBuilder(value.length());
135
136
        try {
137 1
            for (int i = 0; i < value.length(); ++i) {
138 1
                final char c = value.charAt(i);
139
140 1
                if (c < 32 || c > 127 || c == '%' || c == '+') {
141 1
                    escaped.append(URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8.toString()));
142
                } else {
143 1
                    escaped.append(c);
144
                }
145
            }
146
        } catch (UnsupportedEncodingException e) {
147
            throw new IllegalArgumentException("Invalid UTF-8 character", e);
148 1
        }
149
150 1
        return escaped.toString();
151
    }
152
}
153