├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── README.md ├── WechatAirKiss.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── zhchbin │ │ └── airkiss │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── zhchbin │ │ └── airkiss │ │ ├── AirKissEncoder.java │ │ └── MainActivity.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | 8 | # Built application files 9 | *.apk 10 | *.ap_ 11 | 12 | # Files for the Dalvik VM 13 | *.dex 14 | 15 | # Java class files 16 | *.class 17 | 18 | # Generated files 19 | bin/ 20 | gen/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | /*/build/ 26 | 27 | # Local configuration file (sdk path, etc) 28 | local.properties 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | # Log Files 34 | *.log 35 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | AriKiss -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeChatAirKiss 2 | 3 | AirKiss是微信硬件平台提供的一种WIFI设备快速入网配置技术[...](http://iot.weixin.qq.com/document-7_1.html)。`WeChatAirKiss`是通过分析微信客户端相关的网络包实现的Android客户端,实现了相同的功能,使用者能够摆脱微信客户端的限制使用AirKiss技术进行物联网模块的联网配置。 4 | 5 | __声明:请勿将本仓库代码用于任何商业产品。__ 6 | 7 | ## 相关链接 8 | 9 | * [Air Kiss技术实现方案(0.5)](http://wenku.baidu.com/view/a5d51c18561252d380eb6eab.html) 10 | -------------------------------------------------------------------------------- /WechatAirKiss.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "21.1.2" 6 | 7 | defaultConfig { 8 | applicationId "me.zhchbin.arikiss" 9 | minSdkVersion 15 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.0.0' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Program Files\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/me/zhchbin/airkiss/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package me.zhchbin.airkiss; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/me/zhchbin/airkiss/AirKissEncoder.java: -------------------------------------------------------------------------------- 1 | package me.zhchbin.airkiss; 2 | 3 | import java.util.Arrays; 4 | import java.util.Random; 5 | 6 | public class AirKissEncoder { 7 | private int mEncodedData[] = new int[2 << 14]; 8 | private int mLength = 0; 9 | 10 | // Random char should be in range [0, 127). 11 | private char mRandomChar = (char)(new Random().nextInt(0x7F)); 12 | 13 | public AirKissEncoder(String ssid, String password) { 14 | int times = 5; 15 | while (times-- > 0) { 16 | leadingPart(); 17 | magicCode(ssid, password); 18 | 19 | for (int i = 0; i < 15; ++i) { 20 | prefixCode(password); 21 | String data = password + mRandomChar + ssid; 22 | int index; 23 | byte content[] = new byte[4]; 24 | for (index = 0; index < data.length() / 4; ++index) { 25 | System.arraycopy(data.getBytes(), index * 4, content, 0, content.length); 26 | sequence(index, content); 27 | } 28 | 29 | if (data.length() % 4 != 0) { 30 | content = new byte[data.length() % 4]; 31 | System.arraycopy(data.getBytes(), index * 4, content, 0, content.length); 32 | sequence(index, content); 33 | } 34 | } 35 | } 36 | } 37 | 38 | public int[] getEncodedData() { 39 | return Arrays.copyOf(mEncodedData, mLength); 40 | } 41 | 42 | public char getRandomChar() { 43 | return mRandomChar; 44 | } 45 | 46 | private void appendEncodedData(int length) { 47 | mEncodedData[mLength++] = length; 48 | } 49 | 50 | private int CRC8(byte data[]) { 51 | int len = data.length; 52 | int i = 0; 53 | byte crc = 0x00; 54 | while (len-- > 0) { 55 | byte extract = data[i++]; 56 | for (byte tempI = 8; tempI != 0; tempI--) { 57 | byte sum = (byte) ((crc & 0xFF) ^ (extract & 0xFF)); 58 | sum = (byte) ((sum & 0xFF) & 0x01); 59 | crc = (byte) ((crc & 0xFF) >>> 1); 60 | if (sum != 0) { 61 | crc = (byte)((crc & 0xFF) ^ 0x8C); 62 | } 63 | extract = (byte) ((extract & 0xFF) >>> 1); 64 | } 65 | } 66 | return (crc & 0xFF); 67 | } 68 | 69 | private int CRC8(String stringData) { 70 | return CRC8(stringData.getBytes()); 71 | } 72 | 73 | private void leadingPart() { 74 | for (int i = 0; i < 50; ++i) { 75 | for (int j = 1; j <= 4; ++j) 76 | appendEncodedData(j); 77 | } 78 | } 79 | 80 | private void magicCode(String ssid, String password) { 81 | int length = ssid.length() + password.length() + 1; 82 | int magicCode[] = new int[4]; 83 | magicCode[0] = 0x00 | (length >>> 4 & 0xF); 84 | if (magicCode[0] == 0) 85 | magicCode[0] = 0x08; 86 | magicCode[1] = 0x10 | (length & 0xF); 87 | int crc8 = CRC8(ssid); 88 | magicCode[2] = 0x20 | (crc8 >>> 4 & 0xF); 89 | magicCode[3] = 0x30 | (crc8 & 0xF); 90 | for (int i = 0; i < 20; ++i) { 91 | for (int j = 0; j < 4; ++j) 92 | appendEncodedData(magicCode[j]); 93 | } 94 | } 95 | 96 | private void prefixCode(String password) { 97 | int length = password.length(); 98 | int prefixCode[] = new int[4]; 99 | prefixCode[0] = 0x40 | (length >>> 4 & 0xF); 100 | prefixCode[1] = 0x50 | (length & 0xF); 101 | int crc8 = CRC8(new byte[] {(byte)length}); 102 | prefixCode[2] = 0x60 | (crc8 >>> 4 & 0xF); 103 | prefixCode[3] = 0x70 | (crc8 & 0xF); 104 | for (int j = 0; j < 4; ++j) 105 | appendEncodedData(prefixCode[j]); 106 | } 107 | 108 | private void sequence(int index, byte data[]) { 109 | byte content[] = new byte[data.length + 1]; 110 | content[0] = (byte)(index & 0xFF); 111 | System.arraycopy(data, 0, content, 1, data.length); 112 | int crc8 = CRC8(content); 113 | appendEncodedData(0x80 | crc8); 114 | appendEncodedData(0x80 | index); 115 | for (byte aData : data) 116 | appendEncodedData(aData | 0x100); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/me/zhchbin/airkiss/MainActivity.java: -------------------------------------------------------------------------------- 1 | package me.zhchbin.airkiss; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.net.ConnectivityManager; 7 | import android.net.NetworkInfo; 8 | import android.net.wifi.WifiInfo; 9 | import android.net.wifi.WifiManager; 10 | import android.os.AsyncTask; 11 | import android.os.Build; 12 | import android.support.v7.app.ActionBarActivity; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | import android.view.View; 16 | import android.widget.EditText; 17 | import android.widget.Toast; 18 | import java.io.IOException; 19 | import java.net.DatagramPacket; 20 | import java.net.DatagramSocket; 21 | import java.net.InetAddress; 22 | import java.net.SocketException; 23 | import java.net.SocketTimeoutException; 24 | 25 | public class MainActivity extends ActionBarActivity { 26 | private EditText mSSIDEditText; 27 | private EditText mPasswordEditText; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | 34 | mSSIDEditText = (EditText)findViewById(R.id.ssidEditText); 35 | mPasswordEditText = (EditText)findViewById(R.id.passwordEditText); 36 | 37 | Context context = getApplicationContext(); 38 | ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 39 | NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 40 | if (networkInfo.isConnected()) { 41 | final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 42 | final WifiInfo connectionInfo = wifiManager.getConnectionInfo(); 43 | if (connectionInfo != null) { 44 | String ssid = connectionInfo.getSSID(); 45 | if (Build.VERSION.SDK_INT >= 17 && ssid.startsWith("\"") && ssid.endsWith("\"")) 46 | ssid = ssid.replaceAll("^\"|\"$", ""); 47 | mSSIDEditText.setText(ssid); 48 | mSSIDEditText.setEnabled(false); 49 | } 50 | } 51 | } 52 | 53 | public void onConnectBtnClick(View view) { 54 | String ssid = mSSIDEditText.getText().toString(); 55 | String password = mPasswordEditText.getText().toString(); 56 | if (ssid.isEmpty() || password.isEmpty()) { 57 | Context context = getApplicationContext(); 58 | CharSequence text = "Please input ssid and password."; 59 | int duration = Toast.LENGTH_SHORT; 60 | Toast toast = Toast.makeText(context, text, duration); 61 | toast.show(); 62 | return; 63 | } 64 | 65 | new AirKissTask(this, new AirKissEncoder(ssid, password)).execute(); 66 | } 67 | 68 | private class AirKissTask extends AsyncTask implements DialogInterface.OnDismissListener { 69 | private static final int PORT = 10000; 70 | private final byte DUMMY_DATA[] = new byte[1500]; 71 | private static final int REPLY_BYTE_CONFIRM_TIMES = 5; 72 | 73 | private ProgressDialog mDialog; 74 | private Context mContext; 75 | private DatagramSocket mSocket; 76 | 77 | private char mRandomChar; 78 | private AirKissEncoder mAirKissEncoder; 79 | 80 | private volatile boolean mDone = false; 81 | 82 | public AirKissTask(ActionBarActivity activity, AirKissEncoder encoder) { 83 | mContext = activity; 84 | mDialog = new ProgressDialog(mContext); 85 | mDialog.setOnDismissListener(this); 86 | mRandomChar = encoder.getRandomChar(); 87 | mAirKissEncoder = encoder; 88 | } 89 | 90 | @Override 91 | protected void onPreExecute() { 92 | this.mDialog.setMessage("Connecting :)"); 93 | this.mDialog.show(); 94 | 95 | new Thread(new Runnable() { 96 | public void run() { 97 | byte[] buffer = new byte[15000]; 98 | try { 99 | DatagramSocket udpServerSocket = new DatagramSocket(PORT); 100 | DatagramPacket packet = new DatagramPacket(buffer, buffer.length); 101 | int replyByteCounter = 0; 102 | udpServerSocket.setSoTimeout(1000); 103 | while (true) { 104 | if (getStatus() == Status.FINISHED) 105 | break; 106 | 107 | try { 108 | udpServerSocket.receive(packet); 109 | byte receivedData[] = packet.getData(); 110 | for (byte b : receivedData) { 111 | if (b == mRandomChar) 112 | replyByteCounter++; 113 | } 114 | 115 | if (replyByteCounter > REPLY_BYTE_CONFIRM_TIMES) { 116 | mDone = true; 117 | break; 118 | } 119 | } catch (SocketTimeoutException e) { 120 | e.printStackTrace(); 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | 126 | udpServerSocket.close(); 127 | } catch (SocketException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | }).start(); 132 | } 133 | 134 | private void sendPacketAndSleep(int length) { 135 | try { 136 | DatagramPacket pkg = new DatagramPacket(DUMMY_DATA, 137 | length, 138 | InetAddress.getByName("255.255.255.255"), 139 | PORT); 140 | mSocket.send(pkg); 141 | Thread.sleep(4); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | } 145 | } 146 | 147 | @Override 148 | protected Void doInBackground(Void... params) { 149 | try { 150 | mSocket = new DatagramSocket(); 151 | mSocket.setBroadcast(true); 152 | } catch (Exception e) { 153 | e.printStackTrace(); 154 | } 155 | 156 | int encoded_data[] = mAirKissEncoder.getEncodedData(); 157 | for (int i = 0; i < encoded_data.length; ++i) { 158 | sendPacketAndSleep(encoded_data[i]); 159 | if (i % 200 == 0) { 160 | if (isCancelled() || mDone) 161 | return null; 162 | } 163 | } 164 | 165 | return null; 166 | } 167 | 168 | @Override 169 | protected void onCancelled(Void params) { 170 | Toast.makeText(getApplicationContext(), "Air Kiss Cancelled.", Toast.LENGTH_LONG).show(); 171 | } 172 | 173 | @Override 174 | protected void onPostExecute(Void params) { 175 | if (mDialog.isShowing()) { 176 | mDialog.dismiss(); 177 | } 178 | 179 | String result; 180 | if (mDone) { 181 | result = "Air Kiss Successfully Done!"; 182 | } else { 183 | result = "Air Kiss Timeout."; 184 | } 185 | Toast.makeText(getApplicationContext(), result, Toast.LENGTH_LONG).show(); 186 | } 187 | 188 | @Override 189 | public void onDismiss(DialogInterface dialog) { 190 | if (mDone) 191 | return; 192 | 193 | this.cancel(true); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 23 | 24 | 28 | 29 | 35 | 36 | 42 | 43 |